Skip to content

SADAD Integration Cheatsheet

SADAD is the national Electronic Bill Presentment and Payment (EBPP) system for Saudi Arabia, operated under the Saudi Central Bank (SAMA). It provides a centralized platform for presenting and paying bills (utilities, government fees, taxes, telecom, etc.) via all Saudi banks and selected digital channels.

SADAD works with all Saudi banks and digital wallets, allowing customers to pay bills through ATMs, internet banking, phone banking, mobile apps, and other electronic channels, while billers interact with SADAD via their sponsoring bank.

SADAD Account (also called SADAD Online Payment or OLP) is a related service that enables real‑time, non‑card e‑commerce payments by debiting a designated “light account” at the issuing bank during online checkout, typically using an alias or SADAD ID instead of a card number.

AspectSADAD Bills (EBPP)SADAD Account (OLP / SADAD Number)
Primary use caseRecurring and one‑off bills (utilities, government, telecom, etc.)E‑commerce checkout, direct online payments
Payment channelBank channels (ATM, online banking, mobile app, IVR, branch)Merchant checkout flows via SADAD platform and bank portals
FlowBiller uploads bills to SADAD via bank; customers query and pay via bank; SADAD settles and reconciles dailyCustomer selects SADAD at checkout, authenticates via bank, funds debited in real time to merchant
IdentifierBiller ID + bill/invoice referenceSADAD Account ID / alias or SADAD Number code
SettlementBatch settlement via SARIE and daily reportsTypically real‑time or near real‑time settlement to merchant account
  • Core rail for bill payments and government fees; cabinet resolutions require government entities collecting revenues electronically to integrate via SADAD.
  • Complements other rails like MADA (domestic card network), credit/debit cards, and newer wallets by giving a familiar alternative that uses existing bank accounts, not cards.
  • Use SADAD when:
    • Dealing with government, utilities, or regulated bill‑like payments.
    • Customers are used to paying via their bank channels (billers list, SADAD Number) rather than card entry.
    • You need guaranteed funds from the customer’s bank account with low fraud risk and strong customer authentication.
  • Use MADA/cards when:
    • Classic e‑commerce with cardholder experience optimization (one‑click, saved cards).
    • International cards or non‑Saudi customers.
    • You rely on PSP risk tools and card‑network chargebacks.

⚠️ SADAD is strongly regulated; any integration must follow the sponsoring bank/PSP’s onboarding, security, and certification processes.


Because SADAD itself does not expose a single public API, integration is typically via banks or PSPs that provide their own REST/SOAP interfaces.

2.1 Direct integration via bank (EBPP / SADAD Bills)

Section titled “2.1 Direct integration via bank (EBPP / SADAD Bills)”
  • You onboard as a biller with a specific bank (or multiple banks) that sponsors you into SADAD.
  • The bank exposes:
    • File‑based or API‑based bill upload interface (batch or real time).
    • Payment notification / settlement reports (often files or SFTP + portal).
    • Optional online APIs for inquiry and payment notifications.
  • Common tech characteristics:
    • VPN or leased line connectivity, mutual TLS, sometimes SOAP or ISO‑8583 with bank‑specific schemas.
    • Strict change control, long lead times for onboarding and certification.

2.2 Integration via PSP (HyperPay, PayTabs, others)

Section titled “2.2 Integration via PSP (HyperPay, PayTabs, others)”
  • PSP exposes a unified REST API and/or hosted payment page that includes SADAD as a payment option; under the hood, PSP connects to banks/SADAD.
  • Typical features:
    • PSP‑managed SADAD onboarding.
    • One contract and settlement channel for multiple payment methods (MADA, cards, SADAD, wallets).
    • Sandbox/testing environments and developer documentation for integration.
ModelDescriptionProsCons
Hosted page (PSP)Customer is redirected (or embedded iFrame) to PSP page that offers SADAD, cards, etc. Payment result sent via redirect + webhookFast to integrate, PSP handles UX, security, and certificationsLess control over UX, PSP‑specific limitations
API‑based (server‑to‑server)Your backend calls PSP/bank APIs to create SADAD bills or payment sessions; front‑end uses bank/PSP URLs or SADAD referencesMore control, easier multi‑channel flows (web, app, POS)Higher responsibility for security, error handling, PCI‑adjacent concerns
  • Direct‑via‑bank: ✅ deeper control, potentially better fees; ⚠️ higher complexity, slower onboarding, multi‑bank differences.
  • Via PSP: ✅ faster time‑to‑market, unified API, better documentation; ⚠️ PSP lock‑in, extra fees, need abstraction layer to switch providers.

  • Biller ID: Unique numeric identifier assigned by SADAD to each biller (e.g., utility company, government agency, private merchant).
  • SADAD Account / OLP ID: Alias‑based online payment account for e‑commerce (light account) that customers register with their bank; used at checkout instead of card number.
  • Invoice/Bill ID (Customer Reference): Biller‑side reference for a specific bill; combined with Biller ID to uniquely identify the obligation in SADAD.
  • SADAD Number / Payment Reference:
    • For SADAD Bills: reference number exposed to customer for bank payment (may be customer number or invoice reference, depending on biller design).
    • For PSP‑style SADAD: PSP often generates a SADAD Number code which the customer uses in their bank app to complete payment.
  • Reconciliation:
    • Process of matching SADAD‑reported paid bills with internal invoices, based on Biller ID, reference number, amount, value date, and banking transaction IDs.

4.1 SADAD Bills (EBPP) – High‑level steps

Section titled “4.1 SADAD Bills (EBPP) – High‑level steps”
  1. Biller sends summary bill information to SADAD at a pre‑determined schedule via its sponsoring bank.
  2. SADAD validates and loads bills into its database, notifies biller of any discrepancies.
  3. Customer queries and views bills through bank channels (ATM, online banking, mobile app, etc.).
  4. Bank forwards inquiry to SADAD; SADAD returns bill details from its database.
  5. Customer selects bill(s) and confirms payment; bank debits customer account and confirms transaction.
  6. SADAD updates its database and notifies relevant biller(s) of successful payments.
  7. At end of day, SADAD initiates settlement via SARIE and sends reconciliation reports to billers.

4.2 Text‑based sequence diagram – Direct bill flow

Section titled “4.2 Text‑based sequence diagram – Direct bill flow”
Participant Merchant System
Participant Sponsor Bank
Participant SADAD
Participant Customer Bank
Participant Customer
Merchant System->Sponsor Bank: Upload bills (batch/API) with Biller ID + Bill Ref
Sponsor Bank->SADAD: Forward bills
SADAD->Sponsor Bank: Validation result
Sponsor Bank->Merchant System: Acknowledgement / rejections
Customer->Customer Bank: Open channels (ATM / online / mobile) and list bills
Customer Bank->SADAD: Request bills for Biller ID / customer
SADAD->Customer Bank: Bill list + amounts + due dates
Customer->Customer Bank: Select bill(s) and confirm payment
Customer Bank->SADAD: Confirm payment(s)
SADAD->Customer Bank: Payment status OK
SADAD->Sponsor Bank: Notify payments
Sponsor Bank->Merchant System: Payment notification(s)
SADAD->SARIE: Settlement instructions (batch)
SARIE->Sponsor Bank: Settlement to biller account

4.3 Text‑based sequence – PSP‑style SADAD Number flow (e‑commerce)

Section titled “4.3 Text‑based sequence – PSP‑style SADAD Number flow (e‑commerce)”
Participant Customer
Participant Merchant Frontend
Participant Merchant Backend
Participant PSP (e.g., PayTabs)
Participant SADAD / Bank
Customer->Merchant Frontend: Place order, select "SADAD" as payment method
Merchant Frontend->Merchant Backend: Create order
Merchant Backend->PSP: Create SADAD payment (amount, orderId, callback URLs)
PSP->Merchant Backend: Payment session + SADAD Number (code)
Merchant Backend->Merchant Frontend: SADAD Number + redirect URL
Merchant Frontend->Customer: Show SADAD Number and instructions
Customer->Bank App: Enter SADAD Number and confirm payment
Bank App->SADAD/Bank: Debit customer and confirm SADAD payment
SADAD/Bank->PSP: Notify payment success
PSP->Merchant Backend (webhook): Payment status = PAID
Merchant Backend->Merchant Frontend: Update UI (order paid)

Because bank and PSP APIs are heterogeneous, design your internal interface as an abstraction with provider‑specific adapters.

Assume an internal PaymentService that exposes generic endpoints independent of bank/PSP:

POST /api/payments/sadad/bills
GET /api/payments/sadad/bills/{billId}
GET /api/payments/sadad/bills/{billId}/status
POST /api/payments/sadad/webhooks/{provider}

Payload example for bill creation (internal representation):

{
"orderId": "ORD-2026-000123",
"customerId": "CUST-1001",
"amount": 150.75,
"currency": "SAR",
"description": "Electricity Bill March 2026",
"expiryDate": "2026-04-15T23:59:59Z",
"channel": "SADAD_NUMBER",
"provider": "PAYTABS"
}
  • Define a SadadProviderClient interface:
    • createBill(...) – returns provider bill/payment reference (e.g., SADAD Number, bank bill ID).
    • queryStatus(...) – returns normalized status (PENDING, PAID, EXPIRED, FAILED).
    • handleWebhook(...) – maps provider‑specific webhook to internal event.
  • Implement PayTabsSadadClient, HyperPaySadadClient, BankXSadadClient, each mapping internal DTOs to provider‑specific payloads and endpoints.
  • Sync:
    • createBill returns SADAD Number/URL immediately.
    • Status remains PENDING until customer pays via bank.
  • Async:
    • PSP/bank sends webhook/notification when payment is completed (or failed).
    • Your system updates invoice/order status and emits domain events.

⚠️ Never assume payment is completed synchronously at checkout; SADAD is inherently async when customer must complete payment in their bank app/portal.

Terminal window
curl -X POST https://merchant.example.com/api/payments/sadad/bills \
-H "Content-Type: application/json" \
-d '{
"orderId": "ORD-2026-000123",
"customerId": "CUST-1001",
"amount": 150.75,
"currency": "SAR",
"description": "Order #123",
"expiryDate": "2026-04-15T23:59:59Z",
"channel": "SADAD_NUMBER",
"provider": "PAYTABS"
}'

  • application layer (REST controllers): exposes payment APIs to front‑ends and internal services.
  • domain layer:
    • Aggregates: Payment, SadadBill, ReconciliationEntry.
    • Services: SadadPaymentService, ReconciliationService.
  • infrastructure layer:
    • Provider clients: PayTabsSadadClient, BankXSadadClient.
    • HTTP clients (WebClient), persistence repositories (JPA), outbox/event publisher.
public record CreateSadadBillRequest(
String orderId,
String customerId,
BigDecimal amount,
String currency,
String description,
Instant expiryDate,
String channel,
String provider
) {}
public record CreateSadadBillResponse(
String paymentId,
String provider,
String providerReference,
String sadadNumber,
URI bankPaymentUrl,
String status
) {}
public interface SadadPaymentService {
CreateSadadBillResponse createSadadBill(CreateSadadBillRequest request);
PaymentStatusResponse getPaymentStatus(String paymentId);
void handleProviderWebhook(String provider, String payload, Map<String, String> headers);
}
@Configuration
public class HttpClientConfig {
@Bean
public WebClient payTabsWebClient(@Value("${paytabs.base-url}") String baseUrl) {
return WebClient.builder()
.baseUrl(baseUrl)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
}
@Bean
public WebClient bankXWebClient(@Value("${bankx.base-url}") String baseUrl) {
return WebClient.builder()
.baseUrl(baseUrl)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
}
}
@Component
public class PayTabsSadadClient implements SadadProviderClient {
private final WebClient webClient;
private final String profileId;
private final String serverKey;
public PayTabsSadadClient(
@Qualifier("payTabsWebClient") WebClient webClient,
@Value("${paytabs.profile-id}") String profileId,
@Value("${paytabs.server-key}") String serverKey) {
this.webClient = webClient;
this.profileId = profileId;
this.serverKey = serverKey;
}
@Override
public ProviderCreateResponse createBill(ProviderCreateRequest request) {
return webClient.post()
.uri("/payment/request")
.header("authorization", serverKey)
.bodyValue(toPayTabsPayload(request))
.retrieve()
.onStatus(HttpStatusCode::isError, response ->
response.bodyToMono(String.class)
.map(body -> new ProviderException("PayTabs error: " + body)))
.bodyToMono(ProviderCreateResponse.class)
.block();
}
// map to/from PSP payloads...
}
@RestController
@RequestMapping("/api/payments/sadad")
public class SadadPaymentsController {
private final SadadPaymentService sadadPaymentService;
public SadadPaymentsController(SadadPaymentService sadadPaymentService) {
this.sadadPaymentService = sadadPaymentService;
}
@PostMapping("/bills")
public ResponseEntity<CreateSadadBillResponse> createBill(@RequestBody CreateSadadBillRequest request) {
CreateSadadBillResponse response = sadadPaymentService.createSadadBill(request);
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}
@GetMapping("/bills/{paymentId}/status")
public PaymentStatusResponse getStatus(@PathVariable String paymentId) {
return sadadPaymentService.getPaymentStatus(paymentId);
}
}
  • Use @ControllerAdvice to map provider/bank errors to standardized error responses.
  • Distinguish between:
    • Business errors (e.g., bill already paid, expired) → 409 / 422.
    • Technical errors (timeouts, 5xx) → 502 / 503.
  • Log all provider responses with correlation IDs, but never log sensitive data.

✅ Implement circuit breakers and retries at the provider client level (see section 11).


  • PSPs and some banks send webhooks or notifications when SADAD payments are completed.
  • Design a single /api/payments/sadad/webhooks/{provider} endpoint that delegates to provider‑specific handlers.
@PostMapping("/webhooks/{provider}")
public ResponseEntity<Void> handleWebhook(
@PathVariable String provider,
@RequestHeader Map<String, String> headers,
@RequestBody String payload) {
sadadPaymentService.handleProviderWebhook(provider, payload, headers);
return ResponseEntity.ok().build();
}
  • IP whitelisting: restrict webhook source IPs to PSP/bank ranges (enforced at network or WAF level).
  • Signatures: verify HMAC or signature headers if PSP/bank supports it (e.g., X-Signature or similar).
  • TLS: enforce HTTPS with strong ciphers.

⚠️ Never trust only the redirect/return URL for payment success; always rely on webhook+server‑to‑server status confirmation.

  • Webhooks may be delivered multiple times; implement idempotent processing:
    • Use unique provider transaction ID as idempotency key.
    • Persist processed events with status; ignore duplicates.
  • Retry‑safe processing steps:
    • Use transactional outbox: update payment state and publish domain events atomically.

  • SADAD provides reconciliation reports to billers via sponsoring banks, typically daily, with detailed breakdown of processed transactions.
  • PSPs provide transaction reports for SADAD payments via portals and APIs.
  • Match items based on:
    • Biller ID (if available in reports).
    • Internal orderId or billRef vs SADAD reference.
    • Amount, value date, customer ID where available.
  • Use a three‑way match when possible: internal invoice, SADAD/PSP transaction, and bank statement.
  • Mismatches:
    • Payment in SADAD but not in internal system → create suspense entries and investigate.
    • Internal PAID but missing in SADAD report → escalate to bank/PSP.
  • Disputes and refunds:
    • SADAD Account supports refund and dispute flows via consumer portal and bank; merchants often trigger refunds via bank/PSP portals or APIs.
  • Build internal reports for:
    • Collected amount per day per channel (SADAD vs MADA/cards).
    • Ageing of unpaid SADAD bills.
    • Failure reasons (expired, customer cancelled, technical errors).

  • Duplicate bill IDs:
    • Detect in your domain (unique constraint on orderId + channel).
    • If provider reports duplicate, map to 409 CONFLICT for callers.
  • Delayed payments:
    • SADAD allows payments as long as bills are not expired; customers may pay close to expiry.
    • Design logic to accept late but valid payments and adjust fulfilment flows accordingly.
  • Partial payments:
    • Many government/utility bills are full‑amount only; partial payment rules are biller‑specific.
    • Explicitly model whether a SADAD bill supports partial or full payment only; validate before creating bills.
  • Timeouts and retries:
    • For provider calls, use timeouts (e.g., 3–5 seconds) and limited retries with backoff.
    • Do not retry non‑idempotent operations (e.g., createBill) without idempotency keys.

⚠️ Never assume bank/PSP guarantees single delivery or exactly‑once semantics; design for at‑least‑once notifications and at‑least‑once execution.


  • SADAD is under SAMA; government entities collecting revenues must use SADAD for e‑payments, and SADAD is considered a critical national payment system.
  • Banks and PSPs will require security assessments, penetration tests, and possibly SAMA‑aligned controls.
  • Treat SADAD identifiers and bank account references as sensitive.
  • Avoid storing unnecessary PII; tokenize where possible.
  • Use VPN or dedicated links with banks; mutual TLS in all server‑to‑server APIs.
  • Enforce strong TLS configuration and regular certificate rotation.
  • Log all payment lifecycle events (creation, status changes, webhook receipts) with correlation IDs.
  • Separate audit logs from application logs for integrity and retention.

⚠️ Avoid logging full bank account numbers, SADAD Account passwords, or any authentication factors.


  • For createBill and similar operations, use a deterministic idempotency key:
    • idempotencyKey = sha256(orderId + channel + provider).
    • Send this key in a custom header to PSPs that support it; otherwise, enforce idempotency in your own persistence layer.
  • Implement exponential backoff with jitter for transient provider errors.
  • Use different retry policies for read vs write operations.
@Configuration
public class ResilienceConfig {
@Bean
public Customizer<Resilience4JCircuitBreakerFactory> defaultCustomizer() {
return factory -> factory.configure(builder -> builder
.slidingWindowSize(50)
.failureRateThreshold(50)
.waitDurationInOpenState(Duration.ofSeconds(30)),
"sadadProvider");
}
}
  • Wrap provider client calls with circuit breakers and time limiters to avoid cascading failures.
  • Logging:
    • Structured logs with paymentId, orderId, provider, providerTxnId, status.
  • Metrics:
    • Success/failure rates per provider, latency histograms, open circuit counts.
  • Tracing:
    • Use OpenTelemetry to trace from customer request through your system to PSP/bank and back.
  • Run payment services as stateless microservices with horizontal scaling.
  • Use a robust database with HA (e.g., managed PostgreSQL with multi‑AZ).
  • Design webhook handling and reconciliation jobs to be horizontally scalable (e.g., partition by provider or date).

  • PSPs usually provide sandbox environments and test credentials for SADAD and other methods.
  • Banks may provide certification environments over VPN.
  • Use provider mocks for:
    • Bill creation responses (e.g., fixed SADAD Number for given order IDs).
    • Status queries with programmable transitions (PENDING → PAID → SETTLED).
  • Simulate webhook calls with curl to local/staging endpoints.
Terminal window
curl -X POST http://localhost:8080/api/payments/sadad/webhooks/paytabs \
-H "Content-Type: application/json" \
-H "X-Signature: test-signature" \
-d '{
"transaction_reference": "TXN-123",
"order_id": "ORD-2026-000123",
"payment_method": "SADAD",
"result": "SUCCESS"
}'
  • Contract tests for provider adapters (using WireMock or MockWebServer).
  • End‑to‑end tests in staging with real PSP sandbox accounts.
  • Simulate:
    • PSP/bank timeouts and 5xx errors.
    • Duplicate webhooks.
    • Late payments (beyond internal reservation time but before SADAD expiry).

✅ Automate replay of reconciliation files or PSP transaction reports into test environments to validate matching logic.


  • Misunderstanding async nature:
    • Treating SADAD payment as immediate at checkout instead of pending until bank payment completes.
  • Incorrect reconciliation:
    • Not handling timing differences between SADAD settlement and bank statements.
    • Ignoring adjustment/rollback records in reconciliation reports.
  • Tight coupling with bank/PSP APIs:
    • Hard‑coding provider payloads deep in business logic instead of adapter layer.
    • No abstraction for switching providers or adding a second bank.
  • Missing idempotency:
    • Creating multiple SADAD bills for same order on retries.
    • Processing same webhook multiple times.

⚠️ These issues typically surface only at scale during production spikes; design for them from the beginning.


FieldExampleNotes
Biller ID123Assigned by SADAD; numeric.
Bill / Customer Ref2026-03-INV-0001Unique per bill within biller.
SADAD Number1234567890Generated by PSP/bank, used by customer in bank app.
CurrencySARSaudi Riyal.
Amount150.75Typically up to 2 decimal places.
StatusPENDING, PAID, EXPIRED, FAILEDInternal normalized statuses.

Create SADAD bill:

Terminal window
curl -X POST https://merchant.example.com/api/payments/sadad/bills \
-H "Content-Type: application/json" \
-d '{
"orderId": "ORD-2026-000123",
"customerId": "CUST-1001",
"amount": 150.75,
"currency": "SAR",
"description": "Order #123",
"expiryDate": "2026-04-15T23:59:59Z",
"channel": "SADAD_NUMBER",
"provider": "PAYTABS"
}'

Get payment status:

Terminal window
curl -X GET https://merchant.example.com/api/payments/sadad/bills/{paymentId}/status
ScenarioChannelCore actionsOutcome
EBPP bill paymentBank ATM/onlineBiller uploads bills → Customer selects bill in bank channel → Bank debits account → SADAD settles → Biller receives reportsBill marked as paid in SADAD and internal system, funds settled next day.
E‑commerce SADAD NumberPSP hosted/APIMerchant creates SADAD payment via PSP → Customer gets SADAD Number → Pays via bank app → PSP webhook confirmsOrder updated to PAID once webhook processed, fulfilment triggered.

✅ Keep this cheatsheet alongside bank/PSP‑specific integration manuals to quickly map generic concepts and flows to concrete endpoints and payloads for each provider.