Safepay lets you issue QR codes that customers can scan with their banking apps. Static codes are reusable, while dynamic codes embed order-level metadata.
Static vs dynamic
| Attribute | Static QR | Dynamic QR |
|---|
| Reusability | Unlimited | Single use |
| Encoded amount | Optional (usually omitted) | Optional (per order) |
| Encoded order metadata | No | Yes (order ID, request ID) |
| Typical usage | Printed signage, countertop displays | POS terminals, e-commerce checkouts |
Create a static QR
Use this when you want a reusable QR tied to a single aggregator merchant.
Create the QR
Call POST /v1/aggregators/{{aggregator_id}}/qrs with type: STATIC and your aggregator_merchant_identifier.
Render and share
Render data.code using your QR library and display it where customers pay.
curl --request POST "{{base_url}}/v1/aggregators/{{aggregator_id}}/qrs" \
--header "X-SFPY-AGGREGATOR-SECRET-KEY: {{secret_key}}" \
--header "Content-Type: application/json" \
--data '{
"type": "STATIC",
"aggregator_merchant_identifier": "{{aggregator_merchant_identifier}}",
"request_id": "{{request_id}}"
}'
const baseUrl = '{{base_url}}';
const aggregatorId = '{{aggregator_id}}';
const secretKey = '{{secret_key}}';
const aggregatorMerchantIdentifier = '{{aggregator_merchant_identifier}}';
const res = await fetch(`${baseUrl}/v1/aggregators/${aggregatorId}/qrs`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-SFPY-AGGREGATOR-SECRET-KEY': secretKey,
},
body: JSON.stringify({
type: 'STATIC',
aggregator_merchant_identifier: aggregatorMerchantIdentifier,
request_id: crypto.randomUUID(),
}),
});
if (!res.ok) {
const error = await res.json();
throw new Error(`Static QR failed: ${res.status} ${error.message}`);
}
const payload = await res.json();
console.log('QR code', payload.data.code);
Static QRs can receive multiple payments. Reconcile each payment by webhook or by listing QR payments.
Create a dynamic QR
Dynamic codes embed transaction metadata so each scan maps to a unique order.
Create the QR
Call POST /v1/aggregators/{{aggregator_id}}/qrs with type: DYNAMIC, order details, and amount.
Render and collect
Display the QR to the customer and wait for the payment webhook.
curl --request POST "{{base_url}}/v1/aggregators/{{aggregator_id}}/qrs" \
--header "X-SFPY-AGGREGATOR-SECRET-KEY: {{secret_key}}" \
--header "Content-Type: application/json" \
--data '{
"type": "DYNAMIC",
"aggregator_merchant_identifier": "{{aggregator_merchant_identifier}}",
"order_id": "ORDER-4921",
"request_id": "{{request_id}}",
"amount": 7500,
"expiry_in_minutes": 180
}'
const baseUrl = '{{base_url}}';
const aggregatorId = '{{aggregator_id}}';
const secretKey = '{{secret_key}}';
const aggregatorMerchantIdentifier = '{{aggregator_merchant_identifier}}';
const res = await fetch(`${baseUrl}/v1/aggregators/${aggregatorId}/qrs`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-SFPY-AGGREGATOR-SECRET-KEY': secretKey,
},
body: JSON.stringify({
type: 'DYNAMIC',
aggregator_merchant_identifier: aggregatorMerchantIdentifier,
order_id: 'ORDER-4921',
request_id: crypto.randomUUID(),
amount: 7500,
expiry_in_minutes: 180,
}),
});
if (!res.ok) {
const error = await res.json();
throw new Error(`Dynamic QR failed: ${res.status} ${error.message}`);
}
const payload = await res.json();
console.log('QR code', payload.data.code);
Key endpoints
| Endpoint | Purpose |
|---|
POST /v1/aggregators/{{aggregator_id}}/qrs | Create static or dynamic QR codes. |
GET /v1/aggregators/{{aggregator_id}}/qrs | List QR codes with filters and pagination. |
GET /v1/aggregators/{{aggregator_id}}/qrs/{{qr_id}} | Read a single QR code. |
GET /v1/aggregators/{{aggregator_id}}/qrs/{{qr_id}}/payments | List payments made against a QR. |
DELETE /v1/aggregators/{{aggregator_id}}/qrs/{{qr_id}} | Delete a QR code. |
See also