Skip to main content
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

AttributeStatic QRDynamic QR
ReusabilityUnlimitedSingle use
Encoded amountOptional (usually omitted)Optional (per order)
Encoded order metadataNoYes (order ID, request ID)
Typical usagePrinted signage, countertop displaysPOS terminals, e-commerce checkouts

Create a static QR

Use this when you want a reusable QR tied to a single aggregator merchant.
1

Create the QR

Call POST /v1/aggregators/{{aggregator_id}}/qrs with type: STATIC and your aggregator_merchant_identifier.
2

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.
1

Create the QR

Call POST /v1/aggregators/{{aggregator_id}}/qrs with type: DYNAMIC, order details, and amount.
2

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

EndpointPurpose
POST /v1/aggregators/{{aggregator_id}}/qrsCreate static or dynamic QR codes.
GET /v1/aggregators/{{aggregator_id}}/qrsList 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}}/paymentsList payments made against a QR.
DELETE /v1/aggregators/{{aggregator_id}}/qrs/{{qr_id}}Delete a QR code.

See also