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

# RTP Now payment

> Initiate an RTP_NOW payment that prompts the customer immediately.

Use this recipe to collect funds instantly. Submit a Request To Pay payload with the debtor identifier, then wait for the customer to approve inside their banking app.

## Prerequisites

* `{{aggregator_id}}` and `{{secret_key}}`
* `aggregator_merchant_identifier` for the merchant collecting funds
* Debtor IBAN or Raast ID

## Flow summary

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

  <Step title="Validate the debtor account">
    Use the [account validation utilities](/concepts/account-validation) to confirm the debtor details.
  </Step>

  <Step title="Create the RTP Now payment">
    Call `POST /v1/aggregators/{{aggregator_id}}/payments` with `type: RTP_NOW`.
  </Step>

  <Step title="Track the outcome">
    Use webhooks (and optional polling) to surface the final payment status.
  </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>

## RTP now

<Tabs>
  <Tab title="Request Fields">
    <ParamField path="request_id" type="string" required>
      UUID that enforces idempotency. Reuse only when retrying the exact same payload.
    </ParamField>

    <ParamField path="amount" type="integer" required>
      Amount in paisa (PKR minor units). `94000` represents PKR 940.00.
    </ParamField>

    <ParamField path="aggregator_merchant_identifier" type="string" required>
      Token returned in `data.token` when you created the merchant.
    </ParamField>

    <ParamField path="order_id" type="string" required>
      Merchant reference echoed in responses and webhooks.
    </ParamField>

    <ParamField path="type" type="string" required>
      Must be `RTP_NOW` for immediate Request To Pay flows.
    </ParamField>

    <ParamField path="expiry_in_minutes" type="integer">
      Required for RTP Now payments. Valid range 1-180 minutes.
    </ParamField>

    <ParamField path="debitor_iban" type="string">
      Provide when charging a known IBAN. At least one of `debitor_iban`, `debitor_raast_id`, or `debitor_vault_token` must be supplied.
    </ParamField>

    <ParamField path="debitor_raast_id" type="string">
      Supply when requesting payment via a Raast alias (for example, phone number). One of the debtor identifiers is required.
    </ParamField>

    <ParamField path="debitor_vault_token" type="string">
      Reference to a stored payment method for repeat customers. One of the debtor identifiers is required.
    </ParamField>
  </Tab>

  <Tab title="API Example">
    <CodeGroup>
      ```bash cURL theme={null}
      curl --request POST "{{base_url}}/v1/aggregators/{{aggregator_id}}/payments" \
        --header "X-SFPY-AGGREGATOR-SECRET-KEY: {{secret_key}}" \
        --header "Content-Type: application/json" \
        --data '{
          "request_id": "{{request_id}}",
          "amount": 94000,
          "aggregator_merchant_identifier": "{{aggregator_merchant_identifier}}",
          "order_id": "ORDER-RAAST-001",
          "type": "RTP_NOW",
          "expiry_in_minutes": 45,
          "debitor_iban": "PK89SCBL0000001234651601"
        }'
      ```

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

      const rtpResponse = await fetch(
        `${baseUrl}/v1/aggregators/${aggregatorId}/payments`,
        {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'X-SFPY-AGGREGATOR-SECRET-KEY': secretKey,
          },
          body: JSON.stringify({
            request_id: crypto.randomUUID(),
            amount: 94000,
            aggregator_merchant_identifier: aggregatorMerchantIdentifier,
            order_id: 'ORDER-RAAST-001',
            type: 'RTP_NOW',
            expiry_in_minutes: 45,
            debitor_iban: 'PK89SCBL0000001234651601',
          }),
        },
      );

      if (!rtpResponse.ok) {
        const errorPayload = await rtpResponse.json();
        throw new Error(`RTP request failed: ${rtpResponse.status} ${errorPayload.message}`);
      }

      type CreatePaymentPayload = {
        api_version: string;
        data: {
          token: string;
          status: 'P_INITIATED' | 'P_RECEIVED' | 'P_FAILED' | 'P_REJECTED' | 'P_SETTLED';
          request_id: string;
          order_id: string;
          trace_reference?: string;
          msg_id?: string;
          created_at: string;
        };
      };

      const paymentPayload: CreatePaymentPayload = await rtpResponse.json();
      console.log('Payment token', paymentPayload.data.token);
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Sample Response">
    ```json theme={null}
    {
      "api_version": "v1",
      "data": {
        "message": "RTP created successfully",
        "token": "pm_f531c5eb-5c49-40d6-8ac5-012cfd1b24c1",
        "msg_id": "IG2Y50ORB7BE2OLEW0HDVJUIP9QGTL1V6WS",
        "trace_reference": "aa22cc9b-3f55-4613-86cf-9641f11902d1",
        "uetr": "8df9cc1f-77b2-47e4-9a8b-a3050bee6972",
        "status": "P_INITIATED",
        "order_id": "ORDER-RAAST-001",
        "request_id": "{{request_id}}",
        "created_at": "2025-06-26T10:58:17Z"
      }
    }
    ```
  </Tab>
</Tabs>

## Monitor status

Use webhooks as the primary source of truth. Polling is helpful for dashboards or fallback workflows.

```bash cURL theme={null}
curl --request GET "{{base_url}}/v1/aggregators/{{aggregator_id}}/payments/{{payment_token}}" \
  --header "X-SFPY-AGGREGATOR-SECRET-KEY: {{secret_key}}"
```

## Webhook events to expect

* `payment.created`
* `payment.pending_authorization`
* `payment.authorized`
* `payment.completed`
* `payment.settled`
* `payment.rejected`
* `payment.failed`
* `payment.voided`
* `payment.reversed`
* `payment.refunded` (if you issue refunds)
* `payment.refund_partial` (if you issue partial refunds)

Combine polling with webhooks so your UI updates as soon as the customer accepts or rejects the request.

## Troubleshooting

* **Invalid debtor details** - Use the validation utilities before sending the RTP.
* **Expired request** - Regenerate the RTP with a fresh `request_id` and `expiry_in_minutes`.
* **Duplicate payload** - Reusing a `request_id` with different parameters returns an idempotency error; retry with the same body.

## See also

* [Payment lifecycle guide](/guides/payment-lifecycle)
* [Account validation](/concepts/account-validation)
* [Webhooks delivery](/guides/webhooks-delivery)
* [Errors](/reference/errors)
* [Create RTP](/api-reference#post-/v1/aggregators/-raast-aggregator-id-/payments)
