kamiPay LogokamiPay Docs

Printed Dynamic QRs

Create fixed, printable QR codes and attach per-sale charges to them.

Printed Dynamic QRs are permanent, printable QR codes designed for physical points of sale. A single Printed Dynamic QR is created once and placed at a checkout — it never changes. When the merchant is ready to charge a customer, the standard QR Code Creation endpoint automatically attaches a charge to it: the customer scans the same printed QR and their banking app shows the amount to pay.

Charges expire in 5 minutes.

Recommended flow: use the QR Code Creation endpoint (create_dynamic_pix) with the checkout_id and store_id of the checkout that has a Printed Dynamic QR configured. kamiPay will automatically assign the charge to the Printed Dynamic QR and return a linked dynamic QR. Both can be scanned to complete the same payment — whichever the customer scans first settles the transaction.

How charges work

Charges are managed automatically — you don't call a separate charge endpoint. Instead, you configure which checkouts use a Printed Dynamic QR as their default, and from that point the standard create_dynamic_pix call handles everything:

  1. kamiPay detects that the checkout_id + store_id has a Printed Dynamic QR as its default.
  2. A charge is created and attached to the printed QR — the customer scanning it will see the amount.
  3. A standard dynamic QR is also returned in the response, linked to the same charge.
  4. Either QR can be used to pay. Once one is paid, the other is invalidated.
Merchant POS              kamiPay                  Gateway             Customer
      │                      │                         │                   │
      │  POST                │                         │                   │
      │  create_dynamic_pix  │                         │                   │
      │  (checkout_id,       │                         │                   │
      │   store_id, amount)  │                         │                   │
      │─────────────────────►│                         │                   │
      │                      │                         │                   │
      │                      │  Checkout has Printed   │                   │
      │                      │  Dynamic QR             │                   │
      │                      │  → attach charge to QR  │                   │
      │                      │────────────────────────►│                   │
      │                      │◄────────────────────────│                   │
      │                      │                         │                   │
      │◄─────────────────────│                         │                   │
      │  { emv,              │                         │                   │
      │    printed_dynamic_  │                         │                   │
      │    qr_emv,           │                         │                   │
      │    operation_id, … } │                         │                   │
      │                      │                         │                   │
      │  Show dynamic QR     │                         │                   │
      │  (or customer scans  │                         │                   │
      │   the printed one)   │                         │                   │
      │                      │                         │◄──── Scans QR ────│
      │                      │                         │   Payment settled  │
      │                      │◄──────── Webhook ───────│                   │
      │◄──── Notification ───│                         │                   │
      │                      │                         │                   │

Checkout configuration

To enable Printed Dynamic QRs for a checkout, contact the kamiPay team. We will configure the checkout_id and store_id mapping on our end. Once set up, no changes are needed in your integration — the existing create_dynamic_pix call will pick up the Printed Dynamic QR automatically.


Create Printed Dynamic QR

POST /v2/printed_dynamic_qrs

Registers a new permanent QR code linked to a specific checkout and store. If a Printed Dynamic QR already exists for the given checkout_id, the existing one is returned with HTTP 200 instead of 201.

Body Parameters

NameTypeDescription
checkout_idintegerRequired. Identifies the checkout terminal where the QR will be placed. When auto_create_checkout is true, this is the checkout_id that will be assigned to the newly created checkout (not auto-incremented).
store_idintegerOptional. Identifies the store that owns the checkout. Defaults to 1.
auto_create_checkoutbooleanOptional. Defaults to false. When true, kamiPay creates the checkout (using the provided checkout_id) and wires default_qr=printed_dynamic_qr on it before generating the printed QR — letting you provision a checkout and its printed QR in a single call. See the warning below.

auto_create_checkout wallet & store defaults. When you set auto_create_checkout: true, the new checkout inherits its wallet from checkout 1 of the same store_id you sent in the request — not from a generic merchant wallet. If you don't send store_id, it defaults to 1, so the wallet is taken from checkout (store_id=1, checkout_id=1). If you'd rather configure the store, checkout or wallet yourself, use the Stores and Checkouts endpoints instead and then call this endpoint with auto_create_checkout: false (or omitted).

Example Request

const url = `${baseURL}/v2/printed_dynamic_qrs`;

const body = {
  checkout_id: 1,
  store_id: 1,
};

const response = await fetch(url, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${access_token}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify(body),
});
import requests

url = f"{base_url}/v2/printed_dynamic_qrs"

body = {
    "checkout_id": 1,
    "store_id": 1,
}

headers = {
    "Authorization": f"Bearer {access_token}",
    "Content-Type": "application/json",
}

response = requests.post(url, headers=headers, json=body)
package main

import (
  "bytes"
  "encoding/json"
  "fmt"
  "net/http"
)

func main() {
  url := baseURL + "/v2/printed_dynamic_qrs"

  body := map[string]interface{}{
    "checkout_id": 1,
    "store_id":    1,
  }

  requestBody, _ := json.Marshal(body)

  req, _ := http.NewRequest("POST", url, bytes.NewBuffer(requestBody))
  req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", accessToken))
  req.Header.Add("Content-Type", "application/json")

  client := &http.Client{}
  resp, err := client.Do(req)
  if err != nil {
    fmt.Println("Error:", err)
    return
  }
  defer resp.Body.Close()
}

Response

{
  "printed_dynamic_qr_id": "pqr_a1b2c3d4e5f6",
  "qr_emvcode": "00020101021226930014br.gov.bcb.pix...",
  "qr_location_id": "loc_xyz789",
  "address": "0x46d52020**********1fbdcc297d",
  "checkout_id": 1,
  "merchant_id": 42,
  "store_id": 1,
  "network_id": 1,
  "created_at": 1730455200
}
{
  "printed_dynamic_qr_id": "pqr_a1b2c3d4e5f6",
  "qr_emvcode": "00020101021226930014br.gov.bcb.pix...",
  "qr_location_id": "loc_xyz789",
  "address": "0x46d52020**********1fbdcc297d",
  "checkout_id": 1,
  "merchant_id": 42,
  "store_id": 1,
  "network_id": 1,
  "created_at": 1728981000
}
{
  "detail": "Invalid checkout_id or store_id"
}
{
  "detail": "Unauthorized"
}
{
  "detail": "Internal Server Error during printed dynamic QR creation"
}

Response Fields

FieldTypeDescription
printed_dynamic_qr_idstringUnique identifier for this Printed Dynamic QR (prefixed with pqr_).
qr_emvcodestringThe EMV string to render as the permanent, printable QR code.
qr_location_idstringGateway-side location identifier for this QR.
addressstringMerchant wallet address associated with this QR.
checkout_idintegerThe checkout this QR belongs to.
merchant_idintegerThe merchant that owns this QR.
store_idintegerThe store this QR belongs to.
network_idintegerBlockchain network identifier.
created_atintegerUnix timestamp (epoch seconds) of when the Printed Dynamic QR was created.

HTTP 200 means a Printed Dynamic QR already existed for the given checkout_id. The existing record is returned unchanged — no new QR is created.

Auto-creating the checkout in one call

Send auto_create_checkout: true to provision the checkout, wire default_qr=printed_dynamic_qr on it, and generate the printed QR in a single request. If the checkout you point to already exists, kamiPay reuses it silently and only ensures the default_qr config is set.

const response = await fetch(`${baseURL}/v2/printed_dynamic_qrs`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${access_token}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    checkout_id: 500,           // the id you want the new checkout to have
    auto_create_checkout: true, // store_id omitted → defaults to 1
  }),
});

The created checkout's wallet is inherited from checkout 1 of the same store_id. Subsequent calls to create_dynamic_pix for that (store_id, checkout_id) will automatically dispatch to the Printed Dynamic QR flow.

The block (checkout creation + default_qr config) is committed before the gateway call, so a transient gateway failure leaves your checkout and config in place — a retry just needs to finish generating the printed QR.


List Printed Dynamic QRs

GET /v2/printed_dynamic_qrs

Returns the merchant's Printed Dynamic QRs grouped as stores → checkouts → printed_dynamic_qrs. Useful to audit which checkouts already have a printed QR provisioned.

Query Parameters

NameTypeDescription
store_idintegerOptional. Filter by store.
checkout_idintegerOptional. Filter by checkout. When provided without store_id, store_id=1 is assumed.
pageintegerOptional. Defaults to 1.
page_sizeintegerOptional. Defaults to 100 (also the maximum).

Example Request

const url = `${baseURL}/v2/printed_dynamic_qrs?page=1&page_size=100`;

const response = await fetch(url, {
  method: "GET",
  headers: {
    Authorization: `Bearer ${access_token}`,
  },
});
import requests

url = f"{base_url}/v2/printed_dynamic_qrs"

headers = {"Authorization": f"Bearer {access_token}"}
params = {"page": 1, "page_size": 100}

response = requests.get(url, headers=headers, params=params)
package main

import (
  "fmt"
  "net/http"
)

func main() {
  url := baseURL + "/v2/printed_dynamic_qrs?page=1&page_size=100"

  req, _ := http.NewRequest("GET", url, nil)
  req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", accessToken))

  client := &http.Client{}
  resp, err := client.Do(req)
  if err != nil {
    fmt.Println("Error:", err)
    return
  }
  defer resp.Body.Close()
}

Response

{
  "total": 3,
  "page": 1,
  "page_size": 100,
  "stores": [
    {
      "store_id": 1,
      "checkouts": [
        {
          "checkout_id": 4,
          "printed_dynamic_qrs": [
            {
              "printed_dynamic_qr_id": "pqr_a1b2c3d4e5f6",
              "qr_emvcode": "00020101021226930014br.gov.bcb.pix...",
              "address": "0x46d52020**********1fbdcc297d"
            }
          ]
        },
        {
          "checkout_id": 500,
          "printed_dynamic_qrs": [
            {
              "printed_dynamic_qr_id": "pqr_belo500abcdef",
              "qr_emvcode": "00020101021226930014br.gov.bcb.pix...",
              "address": "0x46d52020**********1fbdcc297d"
            }
          ]
        }
      ]
    }
  ]
}

Response Fields

FieldTypeDescription
totalintegerTotal number of Printed Dynamic QRs that match the filters (unpaginated).
pageintegerCurrent page (echoes the request).
page_sizeintegerItems per page (echoes the request).
storesarrayStores that have at least one matching Printed Dynamic QR, each containing its checkouts.
stores[].store_idintegerThe store identifier.
stores[].checkoutsarrayCheckouts within the store that have at least one matching Printed Dynamic QR.
stores[].checkouts[].checkout_idintegerThe checkout identifier.
stores[].checkouts[].printed_dynamic_qrsarrayPrinted Dynamic QRs assigned to the checkout.
stores[].checkouts[].printed_dynamic_qrs[].printed_dynamic_qr_idstringUnique identifier for the QR.
stores[].checkouts[].printed_dynamic_qrs[].qr_emvcodestringEMV string for the printed QR.
stores[].checkouts[].printed_dynamic_qrs[].addressstringMerchant wallet address linked to the QR.

Need to set or inspect the default_qr config (or any other checkout config) directly? See Checkout Configs under Account Administration.

On this page