> ## Documentation Index
> Fetch the complete documentation index at: https://safepay.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Dynamic QR payment

> Generate a per-transaction QR code for Raast payments.

Dynamic QR codes encode order-specific metadata so you can present a scannable Raast request at checkout. Each QR is single-use and maps to a specific aggregator merchant.

## Prerequisites

* `{{aggregator_id}}` and `{{secret_key}}`
* `aggregator_merchant_identifier` for the merchant collecting funds
* Optional order metadata and amount

## Flow summary

<Steps>
  <Step title="Load merchant identifiers">
    Retrieve the `aggregator_merchant_identifier` from the merchant creation response.
  </Step>

  <Step title="Create the dynamic QR">
    Call `POST /v1/aggregators/{{aggregator_id}}/qrs` with `type: DYNAMIC` and the order details.
  </Step>

  <Step title="Render the QR">
    Render the returned `data.code` string as a QR image in your checkout UI.
  </Step>

  <Step title="Track the outcome">
    Listen for payment webhooks or poll the payment endpoint to confirm settlement.
  </Step>
</Steps>

<Callout type="info">Set `{{base_url}}` to `https://dev.api.getsafepay.com/raastwire` in Sandbox or `https://api.getsafepay.com/raastwire` in Production.</Callout>

## Dynamic QR

<Tabs>
  <Tab title="Request Fields">
    <ParamField path="type" type="string" required>
      Use `DYNAMIC` to create a single-use QR code.
    </ParamField>

    <ParamField path="aggregator_merchant_identifier" type="string" required>
      Token returned from merchant creation (`data.token`).
    </ParamField>

    <ParamField path="request_id" type="string">
      Recommended to guarantee idempotency. Reuse only when retrying the same payload.
    </ParamField>

    <ParamField path="order_id" type="string">
      Optional order reference surfaced in webhook payloads.
    </ParamField>

    <ParamField path="amount" type="integer">
      Amount in paisa to embed in the QR; omit to let the payer choose the amount.
    </ParamField>

    <ParamField path="expiry_in_minutes" type="integer">
      Optional expiry window (defaults to 180) after which the QR is invalid.
    </ParamField>
  </Tab>

  <Tab title="API Example">
    <CodeGroup>
      ```bash cURL theme={null}
      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
        }'
      ```

      ```typescript TypeScript theme={null}
      const baseUrl = '{{base_url}}';
      const aggregatorId = '{{aggregator_id}}';
      const secretKey = '{{secret_key}}';
      const aggregatorMerchantIdentifier = '{{aggregator_merchant_identifier}}';

      const qrResponse = 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 (!qrResponse.ok) {
        const errorPayload = await qrResponse.json();
        throw new Error(`Dynamic QR failed: ${qrResponse.status} ${errorPayload.message}`);
      }

      type CreateQrPayload = {
        api_version: string;
        data: {
          token: string;
          code: string;
          type: 'DYNAMIC' | 'STATIC';
          aggregator_merchant_id: string;
          created_at: string;
        };
      };

      const qrPayload: CreateQrPayload = await qrResponse.json();
      console.log('QR code payload', qrPayload.data.code);
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Sample Response">
    ```json theme={null}
    {
      "api_version": "v1",
      "data": {
        "token": "qr_4076ad61-75cf-44a3-97a1-b66643ea3bf2",
        "aggregator_merchant_id": "am_be454a50-7612-4dc6-a97e-284ebbe7ae93",
        "type": "DYNAMIC",
        "code": "0002010102122876003285bc899c1dd443e3b3beb24631aef093...",
        "created_at": "2025-04-28T10:26:48Z"
      }
    }
    ```
  </Tab>
</Tabs>

## Render the QR

Feed `data.code` into your preferred QR library (for example, `qrcode.react`, `qr-image`, or `qrcodejs`). The returned string is EMVCo-compliant and ready for Raast banking apps.

## Webhook events to expect

* `payment.created`
* `payment.completed`
* `payment.settled`
* `payment.failed`
* `payment.rejected`

When the customer pays, monitor the associated payment token via `GET /v1/aggregators/{{aggregator_id}}/payments/{{payment_id}}` or your webhook handler to confirm settlement.

## See also

* [Payment lifecycle guide](/guides/payment-lifecycle)
* [QR codes](/concepts/qr_code)
* [Webhooks delivery](/guides/webhooks-delivery)
* [Create QR](/api-reference#post-/v1/aggregators/-raast-aggregator-id-/qrs)
