Skip to main content
Payments initiate a Request-to-Pay (RTP) against a customer’s bank account. Each payment belongs to an aggregator merchant and uses the Raast network to settle instantly once the customer approves.

Endpoint summary

  • Method & path: POST /v1/aggregators/{{aggregator_id}}/payments
  • Authentication: X-SFPY-AGGREGATOR-SECRET-KEY: {{secret_key}}
  • Idempotency: Provide a unique request_id and reuse it when retrying the same intent.
FieldTypeRequired?Notes
request_idstringYesUUID that enforces idempotency.
amountintegerYesAmount in paisa (PKR).
aggregator_merchant_identifierstringYesToken returned when you created the merchant.
order_idstringYesMerchant order reference.
typeenumYesRTP_NOW or RTP_LATER.
expiry_in_minutesintegerRequired for RTP NowRange 1-180 minutes.
expiry_in_daysintegerRequired for RTP LaterRange 1-40 days.
debitor_ibanstringOne ofProvide IBAN when you know the account.
debitor_raast_idstringOne ofProvide Raast alias (for example, phone).
debitor_vault_tokenstringOne ofProvide stored vault token.
Validate the debtor’s IBAN or alias with the account validation utilities before creating the payment.

Example request

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": 7500,
    "aggregator_merchant_identifier": "{{aggregator_merchant_identifier}}",
    "order_id": "ORDER-2025-042",
    "type": "RTP_NOW",
    "expiry_in_minutes": 30,
    "debitor_iban": "PK36SCBL0000001123456702"
  }'
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 aggregatorMerchantIdentifier = process.env.SAFEPAY_AGGREGATOR_MERCHANT_IDENTIFIER;
if (!aggregatorMerchantIdentifier) {
  throw new Error('Missing SAFEPAY_AGGREGATOR_MERCHANT_IDENTIFIER');
}

const paymentResponse = await fetch(
  `${process.env.SAFEPAY_BASE_URL}/v1/aggregators/${process.env.SAFEPAY_AGGREGATOR_ID}/payments`,
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-SFPY-AGGREGATOR-SECRET-KEY': process.env.SAFEPAY_SECRET_KEY ?? '',
    },
    body: JSON.stringify({
      request_id: crypto.randomUUID(),
      amount: 7500,
      aggregator_merchant_identifier: aggregatorMerchantIdentifier,
      order_id: 'ORDER-2025-042',
      type: 'RTP_NOW',
      expiry_in_minutes: 30,
      debitor_iban: 'PK36SCBL0000001123456702',
    }),
  },
);

if (!paymentResponse.ok) {
  const error = await paymentResponse.json();
  throw new Error(`Payment failed: ${paymentResponse.status} ${error.message}`);
}

const paymentPayload: CreatePaymentPayload = await paymentResponse.json();
console.log('Payment status', paymentPayload.data.status);

Status lifecycle

StatusDescription
P_INITIATEDSafepay accepted the request and pushed it to Raast.
P_RECEIVEDCustomer approval pending.
P_AUTHORIZEDPayment authorized and funds on hold.
P_CAPTUREDPayment captured successfully.
P_SETTLEDFunds transferred. Emit confirmation to the user.
P_FAILEDPayment failed during processing.
P_REJECTEDCustomer declined or request rejected.
P_CANCELLEDPayment was canceled before completion.
P_REVERSEDPayment reversed after completion.
P_PARTIALLY_REFUNDEDPayment partially refunded.
P_REFUNDEDPayment fully refunded.

Monitoring

  • Poll GET /v1/aggregators/{{aggregator_id}}/payments/{{payment_id}} for dashboards.
  • Subscribe to webhooks to receive final states.
  • Use request_id to deduplicate retries.

Key endpoints

EndpointPurpose
POST /v1/aggregators/{{aggregator_id}}/paymentsCreate RTP Now or RTP Later payments.
GET /v1/aggregators/{{aggregator_id}}/paymentsList payments with filters and pagination.
GET /v1/aggregators/{{aggregator_id}}/payments/{{payment_id}}Read a single payment.

See also