Skip to main content
Safepay Raast lets you orchestrate Request To Pay flows on behalf of your merchants. Follow this quickstart to get credentials, link a merchant, and send an RTP request.

Prerequisites

  • {{aggregator_id}} issued by Safepay
  • {{secret_key}} for the target environment
  • A Raast merchant to link during merchant creation

Environments

EnvironmentBase URLNotes
Sandboxhttps://dev.api.getsafepay.com/raastwireUse for integration testing. Data resets periodically.
Productionhttps://api.getsafepay.com/raastwireLive traffic. Requests require enabled merchants and approved KYC/KYB.
1

Request an aggregator account

Email support@getsafepay.com with your business context. Safepay provisions an Aggregator, shares your {{aggregator_id}}, and issues the first {{secret_key}}.
2

Secure the aggregator secret key

Store {{secret_key}} in your secret manager and scope access to your backend jobs only. Every Raast API request must send the header X-SFPY-AGGREGATOR-SECRET-KEY: {{secret_key}}.
3

Link a merchant to a Raast merchant

Create an aggregator merchant with a unique merchant_external_id, settlement iban, and a Raast merchant token. The response returns an aggregator_merchant_identifier you will use in every payment request.
4

Trigger your first RTP payment

Use the Create RTP endpoint to request funds. Safepay sends status updates via webhooks.
Never ship {{secret_key}} in client-side code. Rotate credentials immediately if exposed.

Create your first merchant

Each merchant you manage must be linked to a Raast merchant. Set {{base_url}} to https://dev.api.getsafepay.com/raastwire in Sandbox or https://api.getsafepay.com/raastwire in Production.
curl --request POST "{{base_url}}/v1/aggregators/{{aggregator_id}}/merchants" \
  --header "X-SFPY-AGGREGATOR-SECRET-KEY: {{secret_key}}" \
  --header "Content-Type: application/json" \
  --data '{
    "merchant_external_id": "sec_0c3de397-441f-471f-bcd7-b6d948e1c307",
    "iban": "PK62ABPA0010000222380013",
    "name": "Acme Foods Saddar",
    "enabled": true,
    "raast_merchant_id": "{{raast_merchant_id}}",
    "rate_card": {
      "ratecard_kind": "RateCardKind_fixed",
      "fixed_rate": 3000,
      "tax_region": "PK",
      "tax_rate": 0.1
    }
  }'
const baseUrl = '{{base_url}}';
const aggregatorId = '{{aggregator_id}}';
const secretKey = process.env.SAFEPAY_SECRET_KEY ?? '';
const raastMerchantId = '{{raast_merchant_id}}';

const createMerchantResponse = await fetch(
  `${baseUrl}/v1/aggregators/${aggregatorId}/merchants`,
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-SFPY-AGGREGATOR-SECRET-KEY': secretKey,
    },
    body: JSON.stringify({
      merchant_external_id: 'sec_0c3de397-441f-471f-bcd7-b6d948e1c307',
      iban: 'PK62ABPA0010000222380013',
      name: 'Acme Foods Saddar',
      enabled: true,
      raast_merchant_id: raastMerchantId,
      rate_card: {
        ratecard_kind: 'RateCardKind_fixed',
        fixed_rate: 3000,
        tax_region: 'PK',
        tax_rate: 0.1,
      },
    }),
  },
);

if (!createMerchantResponse.ok) {
  const errorBody = await createMerchantResponse.json();
  throw new Error(`Safepay Raast error: ${createMerchantResponse.status} ${errorBody.message}`);
}

type CreateMerchantPayload = {
  api_version: string;
  data: {
    token: string;
    merchant_external_id: string;
    enabled: boolean;
    name: string;
  };
};

const merchantPayload: CreateMerchantPayload = await createMerchantResponse.json();
const aggregatorMerchantIdentifier = merchantPayload.data.token;
console.log('Aggregator merchant identifier', aggregatorMerchantIdentifier);
Persist data.token; Safepay uses it as the aggregator_merchant_identifier in subsequent payment, QR, and payout calls.

Make a payment request

Once Safepay enables the merchant, create a Request-to-Pay (RTP) using the same credentials. Reuse {{base_url}} for the target environment. The example below demonstrates an RTP Now payment that immediately prompts the customer.
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": 5000,
    "aggregator_merchant_identifier": "{{aggregator_merchant_identifier}}",
    "order_id": "INV-2025-0001",
    "type": "RTP_NOW",
    "expiry_in_minutes": 30,
    "debitor_iban": "PK36SCBL0000001123456702"
  }'
const aggregatorMerchantIdentifier = process.env.SAFEPAY_AGGREGATOR_MERCHANT_IDENTIFIER;
if (!aggregatorMerchantIdentifier) {
  throw new Error('Missing SAFEPAY_AGGREGATOR_MERCHANT_IDENTIFIER');
}

const baseUrl = '{{base_url}}';
const aggregatorId = '{{aggregator_id}}';
const secretKey = process.env.SAFEPAY_SECRET_KEY ?? '';

const paymentResponse = 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: 5000,
      aggregator_merchant_identifier: aggregatorMerchantIdentifier,
      order_id: 'INV-2025-0001',
      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}`);
}

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

const paymentPayload: CreatePaymentPayload = await paymentResponse.json();
console.log('Payment token', paymentPayload.data.token);
Next, subscribe to webhooks to know when the customer approves or rejects the request, and retry idempotently by reusing the same request_id when network errors occur.

See also