RsomPay

External API — Partner Integration Guide

API v1.6.0

External API Integration Guide

Create invoices, query status, share payment links, and receive signed webhooks.

Version v1.6.0 B2B Partners English

Integration API base URL

EnvironmentBase URL
Productionhttps://integration.rsompay.com/api/external/v1
Sandboxhttps://demo-integration.rsompay.com/api/external/v1

Payment host

EnvironmentPayment URL pattern
Productionhttps://payment.rsompay.com/pay/invoice/{token}
Sandboxhttps://demo-payment.rsompay.com/pay/invoice/{token}

Prerequisites (from RsomPay)

ItemDescription
External client accountProvisioned by RsomPay for B2B integration
Integration API tokenBearer token named client-external-api
Webhook signing secretProvided by RsomPay IT on onboarding — used with header X-Rsom-Signature (see §8.4)

End-to-end flow

flowchart TB
  subgraph partner [Partner system]
    ERP[ERP backend]
    WH[Webhook endpoint]
    RET[return_url handler]
  end
  subgraph rsom [RsomPay]
    INT[integration.rsompay.com]
    PAY[payment.rsompay.com]
  end
  ERP -->|POST /invoices| INT
  INT -->|payment_url| ERP
  ERP -->|share link| PAY
  PAY -->|browser redirect first| RET
  RET -->|GET by-reference| INT
  INT -.->|webhook later async| WH

Send the integration token on every request:

Authorization: Bearer YOUR_CLIENT_EXTERNAL_API_TOKEN
Content-Type: application/json
Accept: application/json

Token requirements

  • Token name must be client-external-api.
  • Other API tokens return HTTP 403.
  • Required abilities: client_invoices.create, client_invoices.view. Settlement endpoints additionally require client_settlements.view — already granted by default, no extra setup needed.

Example

curl -X POST "https://integration.rsompay.com/api/external/v1/invoices" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d @invoice.json

RsomPay also signs every webhook it sends to you (payment/invoice status updates) with a secret specific to your account, so you can verify the notification is genuine. See §8.4 for the webhook signature — this is separate from the access token above and is not something you need to send on your requests to RsomPay.

Successful responses:

{
  "status": true,
  "message": "Mission completed successfully",
  "data": { }
}

Paginated GET /transactions adds meta: total, per_page, current_page, last_page.

A Payment is a fast, one-shot charge — separate from an Invoice. Use it when you just need to charge a customer now and don't need a long-lived billing document: a failed or abandoned Payment never creates an invoice and never appears in your invoice list.

InvoicePayment
PurposeLong-lived billing document — customer can visit the link and pay wheneverImmediate, one-shot charge
Line itemsYes — items[], discounts, tax per lineNo — single amount
New cardHosted checkout pageHosted checkout page (checkout_url)
Saved cardNot supportedInstant, server-to-server (saved_card_token)

4.1 Create payment

POST /payments

Two ways to charge, chosen by whether you send saved_card_token:

  • New card (no saved_card_token): the response includes a checkout_url — redirect the customer there to enter their card once. return_url is required on this path.
  • Saved card (saved_card_token present): charged instantly, server-to-server, no redirect. Requires the customer to already have an active saved card (see tokenization, §5). return_url is not required on this path.

Request fields

FieldRequiredTypeDescription
external_idYesstringPartner idempotency key. Max 80. Unique per client; a retry with the same value returns the same payment, never a duplicate charge.
amountYesnumberAmount to charge. Min 2.
notification_urlYesURLHTTPS webhook endpoint. Max 2048.
return_urlRequired for the new-card pathURLHTTPS customer redirect after checkout. Ignored when saved_card_token is present. Max 2048.
customerYesobjectcustomer.id (required, max 255) and customer.fullname (required, max 255) — plus optional customer.email, customer.phone (max 32).
saved_card_tokenNostringA token issued via tokenization (§5). When present, charges instantly against that saved card — see behaviour above.
save_cardNobooleanNew-card path only: offer to save the card for future instant charges. Cannot be combined with saved_card_token (422 if both are set).
currency_codeNostringOnly SAR is accepted. Default SAR.
brandNostringPreferred payment method for the new-card checkout page: mada, credit_card (Visa/Mastercard), or apple_pay. Ignored on the saved-card path.
descriptionNostringFree-text description. Max 255.

Sample request — new card

{
  "external_id": "PAY-PARTNER-001",
  "amount": 150,
  "notification_url": "https://partner.example.com/webhooks/rsom",
  "return_url": "https://partner.example.com/payment/return",
  "save_card": true,
  "customer": {
    "id": "CUST-9",
    "fullname": "Ahmed Ali",
    "email": "customer@example.com",
    "phone": "+966501234567"
  }
}

Sample response (201) — new card

{
  "status": true,
  "message": "Mission completed successfully",
  "data": {
    "external_id": "PAY-PARTNER-001",
    "status": "pending",
    "amount": 150.0,
    "currency_code": "SAR",
    "brand": null,
    "description": null,
    "checkout_url": "https://payment.rsompay.com/pay/checkout/...",
    "paid_at": null,
    "created_at": "2026-09-13 10:00:00",
    "updated_at": "2026-09-13 10:00:00"
  }
}

Sample request — saved card

{
  "external_id": "PAY-PARTNER-002",
  "amount": 75,
  "notification_url": "https://partner.example.com/webhooks/rsom",
  "saved_card_token": "rsp_sct_AbCdEf0123456789...",
  "customer": {
    "id": "CUST-9",
    "fullname": "Ahmed Ali"
  }
}

Sample response (201) — saved card

{
  "status": true,
  "message": "Mission completed successfully",
  "data": {
    "external_id": "PAY-PARTNER-002",
    "status": "pending",
    "amount": 75.0,
    "currency_code": "SAR",
    "brand": null,
    "description": null,
    "checkout_url": null,
    "paid_at": null,
    "created_at": "2026-09-13 10:05:00",
    "updated_at": "2026-09-13 10:05:00"
  }
}
The 201 response means "created", not "paid." Both paths confirm asynchronously — poll GET /payments/by-reference or wait for the payment.direct.paid webhook (§4.4).

Errors

HTTPCause
404saved_card_token valid but no active saved card exists for this customer
422Validation failure, or saved_card_token is invalid / does not belong to this customer, or both save_card and saved_card_token were sent

4.2 Query payment status

GET /payments/by-reference?external_id={value}

Look up a payment by your external_id. Response shape is the same as POST /payments, with status reflecting the current state (pending, paid, failed, refunded, partially_refunded).

Errors

HTTPCause
404Payment not found for your client

4.3 Refund payment

POST /payments/{reference}/refund

{reference} is the payment's external_id. Same request/response shape and rules as refunding an invoice (§6.6): omit amount for a full refund, provide it for a partial refund; the 200 response means "submitted", confirmed later by refund_status on the matching transaction (GET /transactions) or the webhook in §4.4.

Refunds are disabled by default — same account-level setting as invoice refunds. Contact support to enable it.

Errors

HTTPCause
403Refunds not enabled for your account, or the payment is currently mid-settlement / already settled
404Payment not found for your client, or no completed collection exists on it
422Refund amount invalid or exceeds the remaining refundable amount, or the gateway rejected the request

4.4 Webhooks

Naming: events starting with payment.direct.* belong to the standalone Payment resource documented on this page. Events starting with payment.* (no direct segment — e.g. payment.completed) belong to an Invoice's underlying payment attempt instead — see §6.7. The two are never the same event.

Payment webhooks use the exact same delivery mechanism, signature (X-Rsom-Signature), and retry policy as invoice webhooks — see §8. Only the event names and payload shape differ. Card-save events (recurring.card_saved) are documented under tokenization (§5.2).

EventWhen
payment.direct.paidPayment succeeded (either path)
payment.direct.failedPayment failed — gateway rejection, declined saved-card charge, or amount-verification mismatch
payment.direct.refundedA refund was confirmed and the payment is now fully refunded
payment.direct.partially_refundedA refund was confirmed but the payment still has a refundable remainder

Sample payload — payment.direct.paid

{
  "event": "payment.direct.paid",
  "event_id": "550e8400-e29b-41d4-a716-446655440000",
  "occurred_at": "2026-09-13T10:00:00+00:00",
  "payment": {
    "external_id": "PAY-PARTNER-001",
    "customer_external_id": "CUST-9",
    "customer_name": "Ahmed Ali",
    "status": "paid",
    "amount": 150.0,
    "currency_code": "SAR",
    "brand": "mada",
    "paid_at": "2026-09-13 10:00:05"
  }
}

Sample payload — payment.direct.refunded

{
  "event": "payment.direct.refunded",
  "event_id": "660e8400-e29b-41d4-a716-446655440001",
  "occurred_at": "2026-09-13T11:00:00+00:00",
  "payment": { "external_id": "PAY-PARTNER-001", "status": "refunded", "amount": 150.0, ... },
  "transaction": {
    "uuid": "770e8400-e29b-41d4-a716-446655440002",
    "reference": "REF-...",
    "status": "completed",
    "settlement_status": "reversed",
    "gross_amount": 150.0,
    "net_amount": 147.0
  }
}

Sample payload — recurring.card_saved

{
  "event": "recurring.card_saved",
  "event_id": "880e8400-e29b-41d4-a716-446655440003",
  "occurred_at": "2026-09-13T10:00:05+00:00",
  "customer_external_id": "CUST-9",
  "mandate_uuid": "990e8400-e29b-41d4-a716-446655440004",
  "brand": "mada",
  "last4": "4242"
}

5.1 Overview

Tokenization lets you charge a customer again later without them re-entering their card. RsomPay supports one flow: save a card while completing a payment, then retrieve a token for that saved card to charge it instantly on a future POST /payments call.

A customer may have more than one saved card — each save produces its own independent, separately-rotatable token. There is no way to tokenize a card independently of a payment (no bare "create token with no charge" endpoint).
StepEndpoint
1. Save a card during a payment§5.2POST /payments with save_card: true
2. Retrieve the saved-card token§5.3POST /customers/{id}/saved-card-token
3. Charge with the token (any time later)§5.4POST /payments with saved_card_token
4. View or remove the saved card§5.4GET / DELETE on saved-cards

5.2 Save a card during a payment

Set save_card: true on the new-card path of POST /payments (no saved_card_token in the same request — the two are mutually exclusive). The customer enters their card once on the hosted checkout page as usual; nothing about the checkout experience changes.

{
  "external_id": "PAY-PARTNER-001",
  "amount": 150,
  "notification_url": "https://partner.example.com/webhooks/rsom",
  "return_url": "https://partner.example.com/payment/return",
  "save_card": true,
  "customer": { "id": "CUST-9", "fullname": "Ahmed Ali" }
}

Once the payment completes and the card is saved, RsomPay sends a recurring.card_saved webhook to the payment's notification_url — this is your signal to call §5.3 and obtain a usable token:

{
  "event": "recurring.card_saved",
  "event_id": "880e8400-e29b-41d4-a716-446655440003",
  "occurred_at": "2026-09-13T10:00:05+00:00",
  "customer_external_id": "CUST-9",
  "mandate_uuid": "990e8400-e29b-41d4-a716-446655440004",
  "brand": "mada",
  "last4": "4242"
}

5.3 Issue the saved-card token

POST /customers/{customer_external_id}/saved-card-token

Call this once you receive recurring.card_saved (§5.2). It returns the token you'll pass as saved_card_token — store it against your own customer record.

Request fields

FieldRequiredTypeDescription
saved_card_idOnly if the customer has more than one saved cardstringWhich saved card to issue a token for — the id from §5.4's list-saved-cards response. If the customer has exactly one active saved card, omit this and that card is used automatically.
Shown once, in plaintext. Calling this again for the same card rotates its token — the previous one stops working immediately, and other saved cards' tokens are unaffected. There is no separate "fetch my existing token" call; if you lose it, issue a new one.
{
  "status": true,
  "message": "Mission completed successfully",
  "data": { "saved_card_token": "rsp_sct_AbCdEf0123456789..." }
}

Errors

HTTPCause
404No matching active saved card for this customer under your account (or saved_card_id doesn't match one)
422The customer has more than one active saved card and saved_card_id was omitted — specify which card

5.4 Charge with a token, view it, or remove it

Pass the token from §5.3 as saved_card_token on POST /payments — the charge is instant and server-to-server, no checkout page involved. Full request/response shapes are documented in §4.1; only the token-related fields are repeated here.

{
  "external_id": "PAY-PARTNER-002",
  "amount": 75,
  "notification_url": "https://partner.example.com/webhooks/rsom",
  "saved_card_token": "rsp_sct_AbCdEf0123456789...",
  "customer": { "id": "CUST-9", "fullname": "Ahmed Ali" }
}

View saved cards

GET /customers/{customer_external_id}/saved-cards

Returns every active saved card for this customer — zero, one, or several — enough to let your customer pick which one to use (brand, last 4 digits), never the full card number.

{
  "status": true,
  "message": "Mission completed successfully",
  "data": [
    { "id": "b1b2c3d4-...", "brand": "mada", "last4": "4242", "created_at": "2026-09-01 10:00:00" },
    { "id": "c2c3d4e5-...", "brand": "apple_pay", "last4": "4242", "created_at": "2026-09-05 10:00:00" }
  ]
}

Remove the saved card

DELETE /customers/{customer_external_id}/saved-cards/{id}

{id} is the value returned by the list call above.

This is local-only. The card stops being usable for any future RsomPay charge, but the card details themselves are held by the payment gateway, not RsomPay — deleting here does not remove anything on the gateway or bank side.

Errors

HTTPCause
404saved_card_token invalid or no longer valid, or no matching active saved card for this customer under your account
422saved_card_token does not belong to this payment's customer

An Invoice is a long-lived billing document with line items — the customer can visit its payment_url and pay whenever they're ready. For a fast one-shot charge with no line items, see Payments (§4) instead.

6.1 Create invoice

POST /invoices

Request fields

Root body
FieldRequiredTypeDescription
external_idYesstringPartner idempotency key. Max 255. Unique per client; duplicates return the same invoice.
notification_urlYesURLHTTPS webhook endpoint. Publicly reachable; no localhost/private IPs. Max 2048.
return_urlYesURLHTTPS customer redirect after checkout. Max 2048.
customerYesobjectCustomer block — see table below.
itemsYesarrayLine items — min 1 object; see table below.
currency_codeNostringCurrency code. Max 8. Default SAR.
due_dateNodateDue date YYYY-MM-DD.
issued_dateNodateIssue date YYYY-MM-DD.
payment_methodNostringPreferred payment method: mada, credit_card (Visa/Mastercard), apple_pay, or tamara. When set, the hosted checkout page skips its own payment-method screen and opens directly into that method's form. Requesting tamara for an account without Tamara enabled returns 422. mada/credit_card/apple_pay requested for a disabled method fall back to showing the picker instead.
notesNostringInvoice notes. Max 2000.
Customer object (customer)
FieldRequiredTypeDescription
customer.fullnameYesstringFull name on payment page. Max 255.
customer.idYesstringYour stable customer reference (e.g. CUST-9). Max 255.
customer.emailNostringValid email. Max 255.
customer.phoneNostringPhone (e.g. +966501234567). Max 32.
Items array (items[])

Each array element is one line item. At least one item is required.

FieldRequiredTypeDescription
items[].descriptionYesstringLine description. Max 255.
items[].quantityNonumberQty. Min 0.001. Default 1.
items[].unit_priceYesnumberUnit price before discount and tax. Min 0.01. Invoice total is computed from all lines.
items[].discount_amountNonumberLine discount amount. Min 0.
items[].tax_rateNonumberTax % (e.g. 15 = 15% VAT).
items[].item_typeNostringCategory label (e.g. service). Max 32.
items[].reference_idNostringYour line reference. Max 128.

Behaviour

  • Idempotent: Same external_id201, same invoice, no duplicate.
  • Initial status: issued.
  • Amount: No root amount field — each line requires unit_price. data.amount and data.totals.grand_total are computed from lines (must be > 0).

Totals example

One line: qty 1, unit 100, discount 10, tax 15% → grand_total: 103.5

Sample request

{
  "external_id": "INV-PARTNER-001",
  "notification_url": "https://partner.example.com/webhooks/rsom",
  "return_url": "https://partner.example.com/payment/return",
  "currency_code": "SAR",
  "due_date": "2026-12-31",
  "issued_date": "2026-05-18",
  "payment_method": "credit_card",
  "notes": "Optional invoice note",
  "customer": {
    "id": "CUST-9",
    "fullname": "Ahmed Ali",
    "email": "customer@example.com",
    "phone": "+966501234567"
  },
  "items": [
    {
      "description": "Service fee",
      "item_type": "service",
      "quantity": 1,
      "unit_price": 100,
      "discount_amount": 10,
      "tax_rate": 15,
      "reference_id": "LINE-1"
    }
  ]
}

Sample response (201)

{
  "status": true,
  "message": "Mission completed successfully",
  "data": {
    "external_id": "INV-PARTNER-001",
    "reference_number": "INV-42-001",
    "status": "issued",
    "amount": 103.5,
    "payment_url": "https://payment.rsompay.com/pay/invoice/...",
    "customer": {
      "id": "CUST-9",
      "fullname": "Ahmed Ali",
      "email": "customer@example.com",
      "phone": "+966501234567"
    },
    "totals": {
      "subtotal": 100,
      "discount_total": 10,
      "tax_total": 13.5,
      "grand_total": 103.5
    },
    "items": [
      {
        "description": "Service fee",
        "item_type": "service",
        "quantity": 1,
        "unit_price": 100,
        "discount_amount": 10,
        "tax_rate": 15,
        "tax_amount": 13.5,
        "total_amount": 103.5,
        "reference_id": "LINE-1"
      }
    ]
  }
}

6.2 Query invoice status

GET /invoices/by-reference?{parameter}={value}

Look up an invoice by your reference. Use this after webhooks or before fulfilling an order.

Provide exactly one query parameter:

ParameterDescription
external_idYour idempotency key (recommended)
reference_numberRsomPay invoice number (e.g. INV-42-001)

Examples

GET /invoices/by-reference?external_id=INV-PARTNER-001
GET /invoices/by-reference?reference_number=INV-42-001
Response shape: The data object is the same as POST /invoices (create invoice). Only HTTP status differs: 200 OK here vs 201 Created on create. Fields such as status reflect the current invoice state (e.g. issued before payment, paid after — see §6.4).

Sample response (200)

{
  "status": true,
  "message": "Mission completed successfully",
  "data": {
    "external_id": "INV-PARTNER-001",
    "reference_number": "INV-42-001",
    "status": "paid",
    "amount": 103.5,
    "payment_url": "https://payment.rsompay.com/pay/invoice/...",
    "customer": {
      "id": "CUST-9",
      "fullname": "Ahmed Ali",
      "email": "customer@example.com",
      "phone": "+966501234567"
    },
    "totals": {
      "subtotal": 100,
      "discount_total": 10,
      "tax_total": 13.5,
      "grand_total": 103.5
    },
    "items": [
      {
        "description": "Service fee",
        "item_type": "service",
        "quantity": 1,
        "unit_price": 100,
        "discount_amount": 10,
        "tax_rate": 15,
        "tax_amount": 13.5,
        "total_amount": 103.5,
        "reference_id": "LINE-1"
      }
    ]
  }
}

Errors

HTTPCause
422No query parameter — provide external_id or reference_number
404Invoice not found for your client

6.3 Payment flow (confirming payment)

  1. Create invoice → receive payment_url.
  2. Share link with your customer.
  3. Customer pays on payment.rsompay.com.
  4. Browser redirect → immediate HTTP redirect to your return_url with query parameters (UX only).
  5. Confirm paymentGET /invoices/by-reference (primary) — call from your return_url handler or right after redirect.
  6. Webhook (backup) → payment.completed / payment.failed arrives later, asynchronously on notification_url (after the redirect) — see §6.7.
Order matters: The customer's browser reaches your return_url first. RsomPay sends the webhook to notification_url afterward (async). Do not wait for the webhook on the return page — confirm with GET /invoices/by-reference first.

On your return_url page, use GET /invoices/by-reference as your main check before you fulfill an order. Confirm data.status is paid; poll briefly if it's still issued (payment may still be confirming).

ChannelWhenRole
return_urlRight after checkoutUX — show success/failure; not proof of payment alone
GET /invoices/by-referenceOn return page (primary)Authoritative — confirm paid before fulfilling
Webhook payment.completedLater (async)Backup — optional reconciliation; still use by-reference if webhook is delayed

return_url query parameters

ParameterDescription
statussuccess, failed, or pending
external_idYour invoice id
reference_numberRsomPay invoice number (e.g. INV-42-001)
payment_referencePayment reference (if available)
amount_paidAmount for this payment
currency_codee.g. SAR
payment_methodHow the customer paid — e.g. card, tamara, or card brand (mada, visa, …) when available
https://partner.example.com/payment/return?status=success&external_id=INV-PARTNER-001&reference_number=INV-42-001&payment_reference=GW-123&amount_paid=103.5&currency_code=SAR&payment_method=card

6.4 Invoice statuses

On data.status and webhook invoice.status for external API invoices:

StatusMeaning
issuedIssued — awaiting payment
paidPaid in full
canceledCancelled — do not collect
failedLast payment attempt failed; customer may try again via payment_url

6.5 Customer transactions

List customer payments across your invoices.

GET /transactions?customer_id={id}&page=1&per_page=20
ParameterRequiredDefaultMax
customer_idYes255
pageNo1
per_pageNo20100

Sample response (200)

Same envelope as other endpoints (status, message, data). data is an array of payment records; paginated responses include meta. payment_method reflects how the customer paid — e.g. card or tamara. refund_status is only present once a refund has been requested on that transaction — poll it to track asynchronous refund confirmation.

{
  "status": true,
  "message": "Mission completed successfully",
  "data": [
    {
      "reference": "TX-REF-1",
      "status": "completed",
      "refund_status": null,
      "amount": 103.5,
      "currency_code": "SAR",
      "payment_method": "card",
      "invoice_external_id": "INV-PARTNER-001",
      "captured_at": "2026-05-18 12:00:00",
      "created_at": "2026-05-18 11:55:00"
    },
    {
      "reference": "TX-REF-2",
      "status": "completed",
      "refund_status": "confirmed",
      "amount": 250,
      "currency_code": "SAR",
      "payment_method": "tamara",
      "invoice_external_id": "INV-PARTNER-002",
      "captured_at": "2026-05-17 15:30:00",
      "created_at": "2026-05-17 15:28:00"
    }
  ],
  "meta": {
    "total": 2,
    "per_page": 20,
    "current_page": 1,
    "last_page": 1
  }
}

6.6 Refund invoice

Refund a previously captured Dhamen payment on one of your invoices — in full or in part.

POST /invoices/{reference}/refund

{reference} is your invoice's external_id or its RsomPay reference_number (same identifiers accepted by GET /invoices/by-reference).

Refunds are disabled by default. Your account must have refunds enabled by RsomPay before this endpoint will accept requests. Contact support to enable it.

Request fields

FieldRequiredTypeDescription
amountNonumberOmit for a full refund of the remaining captured amount. Provide for a partial refund (minimum 1).
reasonNostringFree-text reason, max 500 characters — recorded for audit purposes.

Behaviour

  • A refund can only be requested against a payment that is fully captured and not currently mid-settlement. If the payment is still pending settlement processing, the request is rejected with 403 until settlement finishes.
  • A refund can never exceed the remaining refundable amount on the payment. Repeated partial refunds are summed — once the total reaches the original captured amount, further refund requests are rejected.
  • Refunding the same request twice (e.g. retried after a network timeout) does not create a duplicate refund — retries are safe.
  • The 200 response below means "refund request accepted", not "refund completed." RsomPay submits the refund to the payment gateway and waits for the gateway's own confirmation before the refund is final. Poll GET /transactions and check the refund_status field on the matching transaction to see the outcome:
    refund_statusMeaning
    pending_gatewaySubmitted, awaiting gateway confirmation — not refunded yet.
    confirmedRefund confirmed by the gateway — money has moved.
    stuckNo confirmation received after an extended period — under investigation by RsomPay. Contact support if you see this.
    submit_failedThe gateway rejected the request outright (see the error response below) — safe to correct and retry.

Sample request

{
  "amount": 50.00,
  "reason": "Customer requested partial refund"
}

Sample response (200)

{
  "status": true,
  "message": "Mission completed successfully",
  "data": {
    "reference": "DHAMEN-REFUND-9f2c1e6a-...",
    "refund_status": "pending_gateway",
    "amount": 50.00,
    "currency_code": "SAR",
    "created_at": "2026-07-28 10:15:00"
  }
}

Errors

HTTPCause
403Refunds not enabled for your account, or the payment is currently mid-settlement / already settled
404Invoice not found for your client, or no completed Dhamen payment exists on it
422Refund amount invalid or exceeds the remaining refundable amount, or the gateway rejected the request

6.7 Webhooks

Naming: the events below (payment.*, no direct segment) describe an Invoice's underlying payment attempt. Events named payment.direct.* belong to the separate, standalone Payment resource instead — see §4.4. The two are never the same event.

Invoice webhooks use the shared delivery mechanism, signature, and retry policy documented in Outbound webhooks (§8). No webhook is sent when the invoice is created — only after payment activity.

EventWhen
invoice.status_changedStatus changes after payment (previous_status in payload)
payment.completedPayment succeeded
payment.failedPayment failed (declined by gateway, cancelled by customer, etc. — see payment.reason)
payment.expiredThe checkout session expired before the customer completed payment
payment.refundedA refund on a completed payment was confirmed

Sample payload — payment.completed

{
  "event": "payment.completed",
  "event_id": "550e8400-e29b-41d4-a716-446655440000",
  "occurred_at": "2026-05-18T12:00:00+00:00",
  "customer": { "id": "CUST-9", "fullname": "Ahmed Ali", ... },
  "totals": { "subtotal": 100, "discount_total": 10, "tax_total": 13.5, "grand_total": 103.5 },
  "invoice": {
    "external_id": "INV-PARTNER-001",
    "reference_number": "INV-42-001",
    "status": "paid",
    "amount": 103.5,
    "payment_url": "https://payment.rsompay.com/pay/invoice/..."
  },
  "payment": {
    "uuid": "660e8400-e29b-41d4-a716-446655440001",
    "payment_method": "card",
    "status": "completed",
    "amount": 103.5,
    "paid_at": "2026-05-18 12:00:00"
  }
}

payment_method reflects how the customer paid — e.g. card (Mada/Visa/Mastercard), tamara (Tamara BNPL). Card payments may also return a specific brand when available (e.g. mada, visa).

payment.failed and payment.expired both carry an additional payment.reason field so you can distinguish a gateway decline from a checkout-session expiry from a customer cancellation:

{
  "event": "payment.expired",
  "event_id": "770e8400-e29b-41d4-a716-446655440002",
  "occurred_at": "2026-08-03T12:00:00+00:00",
  "invoice": { "external_id": "INV-PARTNER-001", "reference_number": "INV-42-001", "status": "failed", ... },
  "payment": {
    "uuid": "660e8400-e29b-41d4-a716-446655440001",
    "payment_method": "card",
    "status": "failed",
    "amount": 103.5,
    "paid_at": "2026-08-03 12:00:00",
    "reason": "dhamen_invoice_payment_expired"
  }
}

payment.refunded carries a transaction object instead of payment — the refund state lives on the underlying transaction, not the invoice status:

{
  "event": "payment.refunded",
  "event_id": "880e8400-e29b-41d4-a716-446655440003",
  "occurred_at": "2026-08-03T12:00:00+00:00",
  "invoice": { "external_id": "INV-PARTNER-001", "reference_number": "INV-42-001", "status": "issued", ... },
  "transaction": {
    "uuid": "990e8400-e29b-41d4-a716-446655440004",
    "reference": "TXN-42-001",
    "status": "completed",
    "settlement_status": "reversed",
    "gross_amount": 103.5,
    "net_amount": 101.5
  }
}

A Subscription charges one of your customers a fixed amount on a fixed cadence, on a saved card. It builds on Tokenization (§5) — the customer's first payment always goes through a hosted checkout link that also saves their card, so every later cycle can be charged without the customer present.

billing_modeWho triggers each cycleWhen to use it
automatic (default)RsomPay's own scheduler — it charges the saved card (or issues a pay-link, see collection_method below) every cycle on its own.Standard recurring billing — you just create the subscription once and RsomPay runs it.
merchant_managedYou — RsomPay never auto-charges. You call POST /{id}/charge yourself whenever you want that cycle billed.You already track usage/renewal in your own system and want to control exactly when each charge fires.

Your integration token needs the client_subscriptions.view and client_subscriptions.manage abilities (see §2). Tokens created before Subscriptions was enabled on your account may need to be regenerated to pick these up — if every call below 403s, that's the first thing to check.

7.1 Endpoints

MethodPathPurpose
POST/subscriptionsCreate a subscription (idempotent on external_id)
GET/subscriptionsList your subscriptions
GET/subscriptions/{id}Fetch one subscription
GET/subscriptions/by-external-id/{external_id}Fetch by your own external_id
GET/subscriptions/{id}/cyclesList its billing cycles
GET/subscriptions/{id}/payment-linkFetch the hosted link for whatever cycle is currently due, if any
PATCH/subscriptions/{id}Change amount / interval / billing_mode / collection_method — applies to the next cycle only
POST/subscriptions/{id}/chargeTrigger one charge now (merchant_managed only)
POST/subscriptions/{id}/pausePause — no cycles are billed until resumed
POST/subscriptions/{id}/resumeResume a paused subscription
POST/subscriptions/{id}/cancelCancel, immediately or at the end of the current period
POST/subscriptions/{id}/payment-methodPoint the subscription at a different saved card, or request a new capture link

{id} above is the subscription's id field (a UUID) from any response — not your external_id, which has its own lookup endpoint.

7.2 Create subscription

POST /subscriptions

Idempotent on external_id, scoped to your account: calling this again with an external_id you've already used returns the existing subscription (HTTP 200) instead of creating a duplicate. A genuinely new external_id returns 201.

Request fields

FieldRequiredTypeDescription
external_idYesstringYour idempotency key for this subscription. Max 64, unique per client.
customerYesobjectcustomer.id_number (required — 10-digit Saudi national ID / CR) plus optional customer.name, customer.email, customer.phone, customer.external_id (your own reference for this customer).
titleYesstringMax 150. Shown to the customer on the hosted checkout and in receipts.
amountYesnumberSAR, decimal (not halala). Min 1, max 1,000,000.
intervalYesobjectinterval.unit (required: day, week, month, or year) and interval.count (optional, default 1, max 60 — e.g. {"unit":"month","count":3} bills quarterly).
billing_modeNostringautomatic (default) or merchant_managed — see §7 intro.
collection_methodNostringauto (default) or manual. Only meaningful when billing_mode=automatic: auto charges the saved card directly each cycle, manual has the scheduler email/issue a pay-link instead and wait for the customer to pay it. Under merchant_managed this field is ignored on input and the subscription always reports it as null — that billing mode has no scheduler-driven collection to configure.
billing_anchor_dayNointeger1–28. Pins the billing date to a fixed day of the month (e.g. always the 1st). Only valid when interval.unit is month or year — sending it with day/week returns a 422.
trial_daysNointeger0–365, default 0. The first cycle is still charged immediately via the hosted link (arming the saved card) — trial_days extends that first period by this many free days before the second charge, it does not skip the first charge. Default 0.
start_atNodatetimeDefaults to now. When the first period begins.
total_cyclesNointegerMin 1, max 1200. Stop after this many cycles have been billed.
ends_atNodatetimeMust be after start_at. Stop once this date is reached.
currencyNostringOnly SAR is accepted today. Accepted as a field for forward compatibility; default and only valid value is SAR.
descriptionNostringMax 2000.
notification_urlNoURLHTTPS. Per-subscription webhook target (see §7.9) — falls back to your account-level notification URL when omitted.

total_cycles and ends_at are independent — you can set either, both, or neither. When both are set, the subscription stops at whichever is reached first: e.g. a monthly subscription with total_cycles: 12 and an ends_at three months out will stop after cycle 3, not run all 12.

Sample request

{
  "external_id": "SUB-PARTNER-001",
  "customer": {
    "id_number": "1234567890",
    "name": "Ahmed Ali",
    "email": "customer@example.com",
    "phone": "+966501234567"
  },
  "title": "Monthly gym membership",
  "amount": 149.00,
  "interval": { "unit": "month", "count": 1 },
  "billing_anchor_day": 1,
  "trial_days": 7,
  "notification_url": "https://partner.example.com/webhooks/rsom"
}

Sample response (201)

{
  "status": true,
  "message": "Mission completed successfully",
  "data": { "id": "d290f1ee-6c54-4b01-90e6-d701748f0851", "external_id": "SUB-PARTNER-001", "status": "incomplete", "billing_mode": "automatic", "collection_method": "auto", "..." : "see §7.7 for the full object" },
  "first_payment": {
    "first_payment_url": "https://payment.rsompay.com/pay/checkout/...",
    "first_payment_expires_at": "2026-09-18T12:00:00+00:00"
  }
}

Redirect the customer to first_payment_url to collect the first charge and save their card. The subscription stays incomplete until that payment is confirmed — see §7.9 for the subscription.activated webhook.

7.3 Update subscription

PATCH /subscriptions/{id}

All fields are optional and applied to the next cycle only — never the one already running. Accepts amount, interval (unit/count), billing_anchor_day, billing_mode, collection_method. Sending billing_mode and collection_method together always resolves consistently: if the resulting mode is merchant_managed, collection_method ends up null regardless of what you sent.

7.4 Charge subscription (merchant_managed only)

POST /subscriptions/{id}/charge

Triggers one off-session charge on the saved card right now. Returns 202 — confirmation is asynchronous, via the subscription.renewed or subscription.payment_failed webhook (§7.9). Rejected with 422 on an automatic subscription.

FieldRequiredTypeDescription
idempotency_keyYesstringMax 80. A repeat call with the same key returns the same cycle without charging again — safe to retry on a timeout.
amountNonumberSAR, decimal. Defaults to the subscription's own amount. Must fall within a bounded multiplier of it — an out-of-range amount returns 422.
descriptionNostringMax 255.
period_start / period_endNodatetimeOverride the billed period shown on the resulting cycle/invoice. period_end must be after period_start.
// Request
{ "idempotency_key": "invoice-2026-10-cycle-4", "amount": 149.00 }

// Response (202) — a SubscriptionCycle, see §7.6's cycle fields
{
  "status": true,
  "message": "Mission completed successfully",
  "data": { "sequence": 4, "status": "charging", "origin": "merchant_charge", "amount": 149.0, "..." : "..." }
}

7.5 Pause, resume, cancel, change payment method

POST /subscriptions/{id}/pause     (no body)
POST /subscriptions/{id}/resume    (no body)
POST /subscriptions/{id}/cancel
POST /subscriptions/{id}/payment-method
EndpointFieldDescription
/cancelat_period_end (boolean, default false)false cancels immediately and skips every not-yet-billed cycle. true keeps it active/billing until the current period ends, then cancels.
/payment-methodmandate_ref (string)Point the subscription at a different saved card you already have a mandate reference for. Required unless send_link is used.
/payment-methodsend_link (boolean)Instead of switching cards directly, request a hosted link so the customer can (re)enter a card themselves. Response carries it under a top-level payment_link key. Required unless mandate_ref is used — send exactly one of the two.

Resuming requires the saved card behind the subscription to still be active — a deactivated card returns 422; use /payment-method first.

7.6 List, lookup, and cycles

GET /subscriptions accepts status (one of the values in §7.7's status table), billing_mode (automatic/merchant_managed), external_id, and perPage (max 100). Response shape:

{
  "status": true,
  "message": "Mission completed successfully",
  "data": [ /* array of subscription objects, §7.7 */ ],
  "pagination": { "current_page": 1, "last_page": 3, "per_page": 20, "total": 57, "has_more_pages": true }
}

GET /subscriptions/{id}/cycles returns one entry per billing period — sequence, status (scheduled/invoiced/charging/paid/failed/skipped/refunded), origin, period_start/period_end/due_at, amount, attempts, idempotency_key, paid_at, paid_via.

GET /subscriptions/{id}/payment-link returns whatever hosted link is currently open for this subscription under {"payment_url": "..."} — HTTP 200, or 204 with no body when nothing is currently due (e.g. an automatic/auto-collection subscription with no manual step pending).

7.7 The subscription object

FieldDescription
idUUID — use this in every {id} path above.
external_id, status, billing_mode, collection_methodAs set/resolved — see §7.2.
customername, id_number, email, phone.
title, description, amount, currency, interval, billing_anchor_dayAs set — amount is a SAR decimal, not halala.
trial_days, trial_ends_atTrial configuration and, once active, when it ends.
started_at, current_period_start, current_period_end, next_action_atLifecycle timestamps. next_action_at is null for merchant_managed — nothing is scheduled, you drive it.
ends_at, total_cycles, cycles_completedSee the "whichever comes first" note in §7.2.
cancel_at_period_end, canceled_at, cancel_reasonCancellation state.
paused_at, pause_reasonPause state.
card{brand, last4} of the currently active saved card, when one is attached.
notification_url, first_payment_expires_at, created_at, updated_at 

Status values

StatusMeaning
incompleteCreated, waiting on the first payment link to be paid.
trialingActive, currently inside the free trial extension.
activeBilling normally.
past_dueA charge failed and a retry is scheduled (dunning) — automatic only.
pausedNo cycles are billed until resumed.
canceledTerminal.
completedTerminal — reached total_cycles or ends_at.

7.8 Errors

HTTPCause
404Subscription not found, or belongs to a different client than your token
422Validation error (standard field-keyed shape, see §3/§9), or a domain rule — e.g. /charge on an automatic subscription, an amount outside the allowed range, a state transition that isn't valid from the subscription's current status
500Server error — logged on our side

7.9 Webhook events

Subscription events use the same delivery mechanism, envelope, and signature as every other webhook (§8) — sent to the subscription's own notification_url if set, otherwise your account-level one.

EventWhen
subscription.createdSubscription created (still incomplete)
subscription.first_payment_link_issuedA first-payment link was (re)issued
subscription.activatedFirst payment confirmed — active or trialing
subscription.trial_started / subscription.trial_endedTrial period boundaries
subscription.renewedA cycle was successfully charged
subscription.payment_failedA charge attempt failed
subscription.retry_scheduledDunning scheduled another retry after a failure (automatic only)
subscription.paused / subscription.resumedPause state changed
subscription.payment_method_updatedThe saved card behind the subscription changed
subscription.amount_updated / subscription.interval_updated / subscription.billing_mode_changedFired from PATCH /subscriptions/{id}
subscription.canceled / subscription.completedTerminal transitions
subscription.cycle_refundedA cycle's payment was refunded

A few internal transitions (cycle_invoiced, cycle_charged, dunning_exhausted, collection_method_updated) are recorded on our side but not dispatched as webhooks — they're implementation detail, not partner-facing state changes.

{
  "event": "subscription.renewed",
  "event_id": "9c0e8400-e29b-41d4-a716-446655440099",
  "occurred_at": "2026-10-01T09:00:03+00:00",
  "data": {
    "subscription": {
      "id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
      "external_id": "SUB-PARTNER-001",
      "status": "active",
      "billing_mode": "automatic",
      "collection_method": "auto",
      "customer_identifier": "1234567890",
      "customer_name": "Ahmed Ali",
      "title": "Monthly gym membership",
      "amount": 149.0,
      "currency": "SAR",
      "interval": { "unit": "month", "count": 1 },
      "trial_days": 7,
      "current_period_end": "2026-11-01T00:00:00+00:00",
      "next_action_at": "2026-11-01T00:00:00+00:00",
      "cycles_completed": 4,
      "total_cycles": null,
      "cancel_at_period_end": false
    },
    "event_data": { "sequence": 4 }
  }
}

RsomPay POSTs outbound webhooks to notify you of activity — Invoice events (§6.7), Payment events (§4.4), and account-level Settlement events (below) all share the same delivery mechanism, signature, and retry policy documented here. Delivery is asynchronous — respond with HTTP 2xx quickly and process the payload in the background.

8.1 HTTP request

POST {notification_url}
Content-Type: application/json
X-Rsom-Signature: a1b2c3d4e5f6...

Event details are in the JSON body. For authenticity, read header X-Rsom-Signature (see §8.4).

8.2 Payload envelope

Every event shares these top-level fields, plus a resource-specific object (invoice, payment, or settlement) documented alongside that resource:

{
  "event": "...",
  "event_id": "550e8400-e29b-41d4-a716-446655440000",
  "occurred_at": "2026-05-18T12:00:00+00:00"
}

8.3 Settlement events

EventWhen
settlement.updatedA settlement batch changed state (e.g. moved to transfer_pending or failed)
settlement.paidA settlement batch was paid out in full

settlement.* events are sent to your account-level notification URL (configured once for your integration), not to a per-invoice or per-payment notification_url — a settlement batch aggregates many transactions. Ask your integration contact to register this URL.

{
  "event": "settlement.paid",
  "event_id": "aa0e8400-e29b-41d4-a716-446655440005",
  "occurred_at": "2026-08-03T12:00:00+00:00",
  "settlement": {
    "uuid": "bb0e8400-e29b-41d4-a716-446655440006",
    "batch_no": "STL-20260803120000-AB12",
    "status": "settled",
    "channel": "dhamen",
    "currency_code": "SAR",
    "net_total": 980.00,
    "transactions_count": 12,
    "settled_at": "2026-08-03 12:00:00",
    "failure_reason": null
  }
}

8.4 Webhook signature (X-Rsom-Signature)

RsomPay includes a signature on every webhook so you can confirm the request is genuine, computed with a signing secret unique to your account.

HeaderValue
X-Rsom-Timestamp Unix timestamp (seconds) of when RsomPay sent the webhook.
X-Rsom-Signature Hex-encoded HMAC-SHA256 signature. Read this header from the incoming request and verify it on your server before processing the JSON body.

Verification:

expected_signature = HMAC_SHA256(your_signing_secret, timestamp + "." + raw_request_body)
# reject the webhook if expected_signature != X-Rsom-Signature

Your signing secret is generated for your account and shown (masked, with a copy button) right next to your access token on the "Integration Settings" page in the RsomPay client dashboard. It is independent from the access token — regenerating one does not affect the other. If you rotate it, update your verification code first, since new webhooks are signed with the new secret immediately.

8.5 Implementation requirements

  1. Respond 2xx within a few seconds.
  2. Deduplicate by event_id in the JSON body.
  3. Validate X-Rsom-Signature before trusting the body (see §8.4).

8.6 Retries

  • Default maximum 5 delivery attempts per event.
  • Backoff: approximately 1, 2, 5, 15, then 60 minutes.
  • Timeout per attempt: 15 seconds.
  • Same event_id across retries — deduplicate on your side.
HTTPTypical cause
401Missing or invalid token
403Wrong token type or suspended client
404Invoice not found
422Validation error
500Server error

Common validation issues

IssueFix
URL rejectednotification_url / return_url must be HTTPS and publicly reachable
by-reference 422Provide external_id or reference_number
customer_id on by-referenceNot supported — use GET /transactions instead
payment_method: "tamara" rejected (422 on payment_method)Tamara isn't enabled for your account — omit the field, use mada/credit_card/apple_pay, or contact RsomPay to enable Tamara

Validation messages may be localized — rely on HTTP status and field keys.

  1. Obtain client-external-api token from RsomPay.
  2. Obtain webhook signing secret and verification steps from RsomPay IT (tech@bseeds.sa) for header X-Rsom-Signature (see §8.4).
  3. Import the Postman collection.
  4. POST /invoices with a unique external_id — save payment_url.
  5. GET /invoices/by-reference?external_id=... — confirm status is issued.
  6. Expose an HTTPS webhook endpoint (e.g. webhook.site for sandbox).
  7. Complete a sandbox payment on payment_url — use one of the test cards below.
  8. UX: Browser lands on return_url with query parameters (happens before webhook).
  9. Primary: GET /invoices/by-reference from your return handler — status is paid.
  10. Backup: payment.completed webhook on notification_url arrives later (if delivered).

Test cards (sandbox only)

BrandCard numberExpiryCVV
mada446404000000000701/39100
Mastercard512345000000000801/39100

Cardholder name can be anything (e.g. Test) — it isn't validated in the sandbox. These numbers only work on the sandbox environment (§12, Environments) and never charge real money.

Read-only settlement/payout status for reconciliation. Settlement batches are created and processed internally by RsomPay — this endpoint only exposes their status and constituent transactions, scoped to your own account. Requires the client_settlements.view permission (already granted on all external-partner accounts).

List settlements

GET /settlements?status=settled&page=1&per_page=15
ParameterRequiredNotes
statusNopending, in_progress, transfer_pending, settled, failed, canceled
date_from / date_toNoFilter by batch creation date (ISO date)
page / per_pageNoDefault 15, max 100

Sample response (200)

{
  "status": true,
  "message": "Mission completed successfully",
  "data": [
    {
      "id": 42,
      "reference": "DHM-INS-12-0007",
      "status": "settled",
      "period_from": "2026-07-01",
      "period_to": "2026-07-15",
      "currency_code": "SAR",
      "gross_total": 1500,
      "fee_total": 75,
      "net_total": 1425,
      "transactions_count": 12,
      "bank_transfer_reference": "BANK-REF-001",
      "settled_at": "2026-07-18 09:00:00",
      "created_at": "2026-07-16 08:00:00"
    }
  ],
  "meta": { "total": 1, "per_page": 15, "current_page": 1, "last_page": 1 }
}

Get settlement detail

GET /settlements/{id}

id is the numeric id returned by the list endpoint. Returns 404 if the settlement doesn't exist or doesn't belong to your account — settlements are never shared across clients.

{
  "status": true,
  "message": "Mission completed successfully",
  "data": {
    "id": 42,
    "reference": "DHM-INS-12-0007",
    "status": "settled",
    "period_from": "2026-07-01",
    "period_to": "2026-07-15",
    "currency_code": "SAR",
    "gross_total": 1500,
    "fee_total": 75,
    "net_total": 1425,
    "transactions_count": 12,
    "bank_transfer_reference": "BANK-REF-001",
    "settled_at": "2026-07-18 09:00:00",
    "failure_reason": null,
    "items": [
      {
        "invoice_reference": "INV-42-000123",
        "status": "settled",
        "gross_amount": 125,
        "fee_amount": 6.25,
        "net_amount": 118.75,
        "settled_at": "2026-07-18 09:00:00",
        "transaction": {
          "reference": "TX-REF-1",
          "status": "completed",
          "payment_method": "card",
          "captured_at": "2026-07-16 08:05:00"
        }
      }
    ],
    "created_at": "2026-07-16 08:00:00"
  }
}

No Dhamen/Zoho internal identifiers, ledger references, or platform balance snapshots are exposed on this endpoint by design — only the fields your reconciliation needs. There is currently no outbound webhook for settlement status changes — poll this endpoint periodically (e.g. daily) until one is introduced.

API version

Guide version v1.6.0 — API base path /api/external/v1 (unchanged).

Endpoints quick reference

MethodPathPurpose
POST/paymentsCreate a one-shot payment — new card or instant saved-card charge
GET/payments/by-referenceQuery payment status by external_id
POST/payments/{reference}/refundRefund a payment, full or partial (requires refunds enabled on your account)
POST/customers/{id}/saved-card-tokenIssue (or rotate) a saved-card retrieval token
GET/customers/{id}/saved-cardsList a customer's current saved card, if any
DELETE/customers/{id}/saved-cards/{card_id}Locally deactivate a saved card
POST/invoicesCreate invoice
GET/invoices/by-referenceQuery status (external_id or reference_number)
POST/invoices/{reference}/refundRefund a captured payment, full or partial (requires refunds enabled on your account)
POST/subscriptionsCreate a recurring subscription on a saved card (idempotent on external_id)
GET/subscriptionsList subscriptions
GET/subscriptions/{id}Fetch one subscription
GET/subscriptions/by-external-id/{external_id}Fetch by your own external_id
GET/subscriptions/{id}/cyclesList its billing cycles
GET/subscriptions/{id}/payment-linkFetch the hosted link for the currently due cycle, if any
PATCH/subscriptions/{id}Change amount / interval / billing_mode / collection_method for the next cycle
POST/subscriptions/{id}/chargeTrigger one charge now (merchant_managed only)
POST/subscriptions/{id}/pausePause
POST/subscriptions/{id}/resumeResume
POST/subscriptions/{id}/cancelCancel, immediately or at period end
POST/subscriptions/{id}/payment-methodChange the saved card, or request a new capture link
GET/transactionsCustomer transactions (customer_id required)
GET/settlementsList settlement/payout batches
GET/settlements/{id}Settlement detail with settled items

Environments

ProductionSandbox
Integration APIintegration.rsompay.comdemo-integration.rsompay.com
Payment pagepayment.rsompay.comdemo-payment.rsompay.com

Postman collection

Download Postman collection (JSON)

customer_id in Postman is only for GET /transactions, not invoice lookup.

OpenAPI specification

Download OpenAPI spec (YAML)

Machine-generated from the live route/validation definitions (Scribe) — the authoritative parameter shapes and types behind this hand-written guide. Regenerate after any External API change: php artisan scribe:generate in backend-api, then copy storage/app/private/scribe/openapi.yaml here.

Support

Technical support: tech@bseeds.sa

Website: https://rsompay.com

Changelog

DateNotes
2026-10-01Guide v1.6.0 — added the Subscriptions API (§7): POST /subscriptions (idempotent on external_id, returns a hosted first-payment link that saves the card), list/lookup/cycles/payment-link, PATCH for the next cycle, POST /{id}/charge for merchant_managed subscriptions, pause/resume/cancel/payment-method, and the full subscription.* webhook event set. Builds on Tokenization (§5) and reuses the shared webhook mechanism (§8).
2026-09-13Guide v1.5.0 — standalone-Payment webhook events renamed direct_payment.*payment.direct.* (paid/failed/refunded/partially_refunded) for a name closer to Invoice's own payment.* family while staying unambiguous — see the naming callouts at the top of §4.4 and §6.7. Multiple saved cards per customer are now fully supported (§5): POST /customers/{id}/saved-card-token takes an optional saved_card_id (auto-selected when the customer has exactly one card), and GET /customers/{id}/saved-cards now returns every active card, not just one. Payments now capture the real card/wallet brand used (including Apple Pay and Samsung Pay) after completion, not just the pre-payment preference.
2026-09-13Guide v1.4.0 — added the standalone Payments API (§4): POST /payments (new-card checkout or instant saved-card charge, returns 201), GET /payments/by-reference, POST /payments/{reference}/refund, and new webhook events — a Payment is separate from an Invoice, see the comparison table at the top of §4. Added a dedicated Tokenization section (§5) covering saving a card, issuing/rotating its retrieval token, charging with it, and viewing/removing it, with the recurring.card_saved webhook event moved there. Restructured the whole guide so Payments and Tokenization come right after the foundational sections, all Invoice endpoints (create/query/pay/refund/statuses/transactions/webhooks) are consolidated into one Invoice section (§6), and Outbound webhooks (§8) now holds only the shared delivery/signature/retry mechanics plus account-level settlement events — resource-specific events live with their resource.
2026-08-02Guide v1.3.0 — added GET /settlements and GET /settlements/{id} for read-only settlement/payout reconciliation, scoped to your own account (client_settlements.view).
2026-07-28Guide v1.2.0 — added POST /invoices/{reference}/refund (full/partial refunds, disabled by default, requires an enabled account) and a refund_status field on GET /transactions to track asynchronous refund confirmation.
2026-07-13Guide v1.1.0 — added optional payment_method field to POST /invoices to pre-select mada, credit_card (Visa/Mastercard), apple_pay, or tamara, and skip the payment-method screen on the hosted checkout page.
2026-05-19Guide v1.0.1 — partner-facing fields, payment flow, webhook signature docs
2026-05-18Initial partner guide; docs site split to static hosting (docs.rsompay.com)