Using a Dynamic QR
Generate a per-transaction QR code with an embedded amount — the bridge encodes the checkout total and produces a single-use code ready for the POS display.
A dynamic QR is generated per transaction — at checkout, on an invoice, or in a mobile app. Unlike a static sticker, it embeds a fixed amount in the code itself so the customer just confirms rather than typing a number. The bridge encodes the amount in EMVco Tag 54, sets the point-of-initiation to 12 (one-time), and produces a single-use payload that expires once the payment settles.
The merchant doesn't touch tag numbers, GUID prefixes, or tax condition codes. The bridge reads the merchant wallet — name, city, category, tax regime — and generates the complete network-compliant payload. Creating a dynamic QR is pointing at that wallet and saying what the checkout total is.
What you're building
You'll create a qr-dynamic anchor for a specific checkout amount. The bridge reads the merchant wallet hierarchy, encodes the amount in the payload, and writes a ready-to-scan code back to the anchor. The POS display reads that payload and renders the QR — the customer scans, confirms, and pays.
Prerequisites
- A merchant wallet already created with a complete profile (name, city, category, tax regime, settlement account)
- SDK credentials and a connection to a ledger
- The
qr-dynamicanchor schema deployed on the ledger - A QR bridge connected and configured with a processing policy for anchor enrichment
Create a dynamic QR code
Create the anchor with an amount
minka anchor create| Prompt | What to enter |
|---|---|
| Handle | A QR identifier per transaction, e.g. qr0000099@starbucks.com |
| Schema | qr-dynamic |
| Target | The store wallet, e.g. store1@starbucks.com |
| Amount | The payment amount in base units, e.g. 1500000 for 15,000.00 COP |
| Symbol | The currency handle, e.g. cop |
| Custom | {"reference":"FACT-2024-001234"} |
curl -X POST "https://{ledger-host}/v2/anchors" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer {your-token}" \
-H "x-ledger: {your-ledger}" \
-d '{
"data": {
"handle": "qr0000099@starbucks.com",
"schema": "qr-dynamic",
"target": "store1@starbucks.com",
"amount": "1500000",
"symbol": { "handle": "cop" },
"custom": {
"reference": "FACT-2024-001234"
}
}
}'// SDK setup: see How to Connect to a Ledger
// /moving-money/connect-to-ledger
const { response } = await sdk.anchor
.init()
.data({
handle: 'qr0000099@starbucks.com',
schema: 'qr-dynamic',
target: 'store1@starbucks.com',
amount: '1500000',
symbol: { handle: 'cop' },
custom: {
reference: 'FACT-2024-001234',
},
})
.hash()
.sign([{ keyPair }])
.send()import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
HttpClient client = HttpClient.newHttpClient();
String json = """
{
"data": {
"handle": "qr0000099@starbucks.com",
"schema": "qr-dynamic",
"target": "store1@starbucks.com",
"amount": "1500000",
"symbol": { "handle": "cop" },
"custom": {
"reference": "FACT-2024-001234"
}
}
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://{ledger-host}/v2/anchors"))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer {your-token}")
.header("x-ledger", "{your-ledger}")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString());using System.Net.Http;
using System.Text;
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer {your-token}");
client.DefaultRequestHeaders.Add("x-ledger", "{your-ledger}");
var json = """
{
"data": {
"handle": "qr0000099@starbucks.com",
"schema": "qr-dynamic",
"target": "store1@starbucks.com",
"amount": "1500000",
"symbol": { "handle": "cop" },
"custom": {
"reference": "FACT-2024-001234"
}
}
}
""";
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync(
"https://{ledger-host}/v2/anchors", content);
var body = await response.Content.ReadAsStringAsync();The curl examples use a Bearer token for authentication. In production, every mutation also requires a cryptographic signature — see Sign Payments for the full signing flow. The TypeScript SDK handles signing automatically.
The bridge reads store1@starbucks.com, sees type: "store", and walks up to main@starbucks.com for the merchant-level fields. It resolves the MCC from the category, detects the alias type from the taxId, selects the correct national standard by country code, and generates the TLV payload — this time also encoding the amount in Tag 54 and marking the code as single-use.
| Field | What it does |
|---|---|
| amount | The payment amount in the currency's base units. 1500000 means 15,000.00 COP (2 decimal places). |
| symbol | The currency. The bridge maps the handle to the ISO 4217 numeric code for the payload (cop → 170). |
| reference | Your invoice or order number — included in the payload so you can reconcile the payment against your system. |
The amount encoding uses the currency profile's decimal configuration — for COP, 2 decimal places, so 1500000 base units → 15000.00 in the Tag 54 value. For currencies with no decimal places (like CLP), the conversion is direct. The symbol handle maps to the ISO 4217 numeric code written into the payload (cop → 170, usd → 840).
A standard regime merchant gets IVA and INC condition codes computed automatically from the wallet's tax configuration. You can override the merchant's default tax treatment for a specific transaction by passing custom.taxRegime on the anchor — for example, "exempt" for a promotional sale skips the tax block entirely, while "simplified" selects the condition code for small merchants. This lets you handle edge cases without changing the wallet's default configuration.
Display at checkout
Read the anchor and display the QR on the POS screen:
minka anchor read qr0000099@starbucks.comcurl "https://{ledger-host}/v2/anchors/qr0000099@starbucks.com" \
-H "Authorization: Bearer {your-token}" \
-H "x-ledger: {your-ledger}"// SDK setup: see How to Connect to a Ledger
// /moving-money/connect-to-ledger
const { anchor } = await sdk.anchor.read('qr0000099@starbucks.com')
console.log(anchor.data.custom.qr.payload) // full TLV string — encode into a QR image
console.log(anchor.data.custom.qr.paymentId) // network transaction ID for inbound routing
console.log(anchor.data.custom.qr.generatedAt) // when the payload was generated
// renderQRImage(anchor.data.custom.qr.payload) // your QR rendering libraryimport java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://{ledger-host}/v2/anchors/qr0000099@starbucks.com"))
.header("Authorization", "Bearer {your-token}")
.header("x-ledger", "{your-ledger}")
.GET()
.build();
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString());using System.Net.Http;
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer {your-token}");
client.DefaultRequestHeaders.Add("x-ledger", "{your-ledger}");
var response = await client.GetAsync(
"https://{ledger-host}/v2/anchors/qr0000099@starbucks.com");
var body = await response.Content.ReadAsStringAsync();The paymentId follows the format CO.COM.RBM.TRXID{timestamp}{random} — this is the identifier the external network uses to route inbound payments back to this anchor. The bridge also writes it to meta.labels for reverse lookup when the payment arrives.
The customer scans, sees the amount pre-filled in their bank app, confirms, and pays. There's nothing for the cashier to verify — the amount on the customer's screen matches the checkout total exactly.
Dynamic QR codes are single-use. Once the payment settles, the anchor is consumed and the paymentId is marked as used by the network. If a customer scans after settlement, their bank app will return an error indicating the QR is no longer valid. For timeout or abandon scenarios, you can delete the anchor explicitly or let it expire via a TTL policy on the schema.
Amount encoding
The ledger stores amounts as integers in the currency's base unit — there are no floating-point values. The bridge converts to the display amount for the EMVco payload using the currency's decimal configuration.
For Colombian pesos (COP), the decimal count is 2, so the bridge divides by 100 to produce the Tag 54 value: 1500000 → 15000.00. For Chilean pesos (CLP), the decimal count is 0, so 15000 stays 15000. The symbol handle (cop, usd, clp) maps to the ISO 4217 numeric code embedded in the currency tag of the payload.
If you pass an amount that doesn't match the currency's expected precision — for example, 1500001 when the currency has 2 decimal places — the bridge will reject the anchor with a validation error before generating any payload.
Tax override per transaction
The merchant wallet carries a default taxRegime (typically standard, simplified, or exempt) that the bridge uses for every QR it generates. For most transactions this is correct and you don't need to think about it.
For edge cases — a promotional item sold tax-free, a transaction between registered entities that changes the applicable regime — pass custom.taxRegime directly on the dynamic anchor:
{
"data": {
"handle": "qr0000100@starbucks.com",
"schema": "qr-dynamic",
"target": "store1@starbucks.com",
"amount": "50000",
"symbol": { "handle": "cop" },
"custom": {
"reference": "PROMO-2024-0042",
"taxRegime": "exempt"
}
}
}The per-transaction override does not change the merchant's wallet configuration. It applies only to this anchor's payload.
Static vs. dynamic
A static QR lives as a permanent sticker on the counter — one anchor, unlimited scans, the customer enters the amount. A dynamic QR is generated per transaction — one anchor, one payment, the amount is embedded and locked.
Use dynamic when the checkout total is known at the time of display: POS terminal, invoice, in-app payment request. Use static when the merchant handles a wide range of amounts without a POS system, or when the infrastructure to generate a QR per transaction isn't available yet. See Collect with a Static QR for the static flow.
What you learned
- How to create a
qr-dynamicanchor with an amount, currency, and reference - How the bridge encodes the amount into EMVco Tag 54 using the currency's decimal configuration
- How
symbolmaps to ISO 4217 numeric codes in the payload - How to override the tax regime per transaction without changing the merchant wallet
- Why dynamic QR codes are single-use and what happens after settlement
Common errors
| Error | Cause | Fix |
|---|---|---|
amount-exceeds-limit | The amount exceeds the maximum allowed by the schema or symbol configuration | Check the symbol's maximum transaction amount with minka symbol read {symbol} |
anchor-expired | The dynamic QR's TTL has elapsed before the customer scanned it | Generate a new anchor — dynamic QRs are single-use and time-limited |
handle-already-exists | An anchor with this handle already exists | Use a unique handle per transaction (e.g., include a timestamp or order ID) |
Next steps
- Collect with a Static QR — permanent counter sticker for open-amount payments
- Onboard a Merchant — set up the merchant wallet that QR anchors point to
- About Schemas — how anchor schemas define what the bridge generates
Using Static QR
Create a permanent QR code for a store counter — the bridge reads the merchant profile and generates the complete network payload in a single API call.
Using an Alphanumeric Anchor
Register a short business alias as a payment point — customers pay by typing the identifier instead of scanning a QR code.