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.
Generating a QR code for payment collection traditionally means assembling dozens of network-specific fields — merchant category codes, tax breakdowns, acquirer identifiers, terminal IDs, alias types — and encoding them into a TLV payload that follows the correct national standard. Every country has its own specification (EMVCo in Colombia, PIX in Brazil, PromptPay in Thailand), and getting a single field wrong produces a QR that scanners reject silently.
With Minka, the merchant already described who they are when they created their wallet. Creating a static QR is pointing to that wallet and saying this is a permanent payment point. The bridge reads the merchant profile — name, city, category, tax regime — maps the category to an MCC, detects the alias type from the taxId, and generates the complete network payload. The merchant never touches tag numbers, GUID prefixes, or country-specific tax condition codes.
What you're building
In this guide you'll create a static QR code: a permanent sticker for a store counter that customers scan with their bank app and enter the amount themselves. It uses the qr-static anchor schema, which tells the bridge to generate a reusable payload with point-of-initiation set to 11. The code never expires and can be used for unlimited payments.
Prerequisites
- A merchant wallet already created with a complete profile
- SDK credentials and a connection to a ledger
- The
qr-staticanchor schema deployed on the ledger - A QR bridge connected and configured with a processing policy for anchor enrichment
Create a static QR code
A static QR is a permanent payment point. Print it as a sticker, table tent, or receipt footer. The customer scans it with their bank app and enters the amount themselves. It never expires and can be used for unlimited payments.
Create the anchor
minka anchor create| Prompt | What to enter |
|---|---|
| Handle | A QR identifier in the merchant's domain, e.g. qr0000001@starbucks.com |
| Schema | qr-static |
| Target | The store wallet, e.g. store1@starbucks.com |
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": "qr0000001@starbucks.com",
"schema": "qr-static",
"target": "store1@starbucks.com"
}
}'// SDK setup: see How to Connect to a Ledger
// /moving-money/connect-to-ledger
const { response } = await sdk.anchor
.init()
.data({
handle: 'qr0000001@starbucks.com',
schema: 'qr-static',
target: 'store1@starbucks.com',
})
.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": "qr0000001@starbucks.com",
"schema": "qr-static",
"target": "store1@starbucks.com"
}
}
""";
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": "qr0000001@starbucks.com",
"schema": "qr-static",
"target": "store1@starbucks.com"
}
}
""";
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.
Three fields. The bridge does the rest.
When the ledger receives this anchor creation, the processing policy intercepts it and forwards the request to the QR bridge. The bridge reads store1@starbucks.com (the store wallet), sees type: "store", and walks up to main@starbucks.com for the merchant-level fields. It resolves the MCC from "Fast food restaurant" → 5814, detects the alias type from the taxId 900123456-7 → NIT (subtag 01), selects the Colombia EMVco profile by country code, computes the tax tags from taxRegime: "standard", and generates the complete TLV payload with CRC-16 checksum. Because the schema is qr-static, the point-of-initiation is set to 11 (reusable) and no amount tag is embedded.
Read the generated QR
The bridge writes the generated data back to the anchor's custom.qr namespace:
minka anchor read qr0000001@starbucks.comcurl "https://{ledger-host}/v2/anchors/qr0000001@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('qr0000001@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 generatedimport 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/qr0000001@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/qr0000001@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.
Pass custom.qr.payload to any QR image library to produce a scannable code. The payload is a complete, standards-compliant string ready for encoding — no post-processing required.
The bridge makes sure the generated QR is valid for the local payment network in the merchant's country. For a Colombian merchant, it produces an EMVco-compliant payload with the correct acquirer GUID, alias routing tags, MCC code, currency, and tax condition codes per the bre-b standard. For a Brazilian merchant, it would produce a PIX payload. For Thailand, PromptPay. The merchant never needs to know which standard applies — the bridge selects the right one from the country field on the merchant wallet.
Print and deploy
Encode the payload into a QR image and print it. The code is permanent — as long as the anchor exists on the ledger, the QR is valid. Customers scan it, their bank app reads the embedded merchant information (name, city, MCC, acquirer GUID, alias), and the payment routes through to the merchant's settlement account automatically.
What happens after the customer pays
The payment flow is fully automatic once the QR exists.
The customer's bank sends a payment through the connected network. The payment message carries the QR's paymentId — the unique identifier the bridge embedded in the payload.
The ledger receives the inbound intent. An external bridge (ACH, RBM, or whatever network the customer's bank uses) creates an intent that moves funds from the customer's bank to the hub bank's settlement account (svgs:1234@bank.co).
The settlement effect fires. When the inbound intent commits, an effect triggers the QR bridge. The bridge looks up the anchor by paymentId (via the meta.labels reverse index), resolves it to store1@starbucks.com, walks up to the merchant wallet for the settlement field (svgs:4242@bank.co), and creates a second intent that credits the merchant's bank account.
The merchant is notified. When the merchant credit intent commits, a second effect fires, and the bridge sends a notification to the POS terminal or merchant app.
Two intents, two commits, fully atomic. Intent 1 reflects the network movement (auditable, matches bank reconciliation). Intent 2 is the internal book entry (merchant gets credited). If either fails, both roll back.
What you learned
- How to create a static QR anchor with just a target wallet reference
- How the bridge reads the wallet hierarchy and generates the complete EMVco payload with point-of-initiation
11 - How the two-intent settlement pattern credits the merchant automatically after each scan
Common errors
| Error | Cause | Fix |
|---|---|---|
bridge-unreachable | The QR bridge is not available to generate the payload | Check bridge status and retry — the anchor creation will be retried automatically |
merchant-profile-incomplete | The parent merchant wallet is missing required fields (tax ID, category, or country) | Update the merchant wallet with the missing fields before creating the QR anchor |
handle-already-exists | A static QR anchor with this handle already exists | Query the existing anchor or use a different handle |
Next steps
- Collect with a Dynamic QR — generate a per-transaction QR with a fixed amount embedded
- Onboard a Merchant — set up the merchant wallet that QR anchors point to
- About Schemas — how anchor schemas control what the bridge generates
Send to an Anchor
Resolve a phone number or alias to get account details, then create a payment intent — the two-step pattern for paying someone you know by identifier, not by account address.
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.