Developer Documentation
Guides Bank API Merchant API Portal →
API v1.0

SadadSD Integration Documentation

SadadSD is Sudan's national interoperability layer for digital merchant payments. This reference covers everything you need to integrate — whether you are a bank / mobile-banking provider or a merchant (e-commerce, POS, or app).

ℹ️
SadadSD does not move funds. It provides a secure interoperability layer — the bank/wallet partner executes the actual transfer and reports the result back via POST /result.

Base URLs

APIBase URL
Bank APIhttps://sadadsd.hithamhaidartech.com/api/bank
Merchant APIhttps://sadadsd.hithamhaidartech.com/api/merchant
Portal APIhttps://sadadsd.hithamhaidartech.com/api/portal

All endpoints are served over HTTPS only. HTTP requests will be redirected.

Authentication

Both Bank and Merchant APIs use API key + secret header authentication. Credentials are issued by SadadSD and must be kept secret — never expose them in client-side code.

HeaderValueDescription
X-API-KeystringYour public API key — identifies your integration.
X-API-SecretstringYour secret — verified server-side with bcrypt. Treat like a password.
⚠️
Rotate your secret immediately if you suspect it has been compromised. Use Portal → Endpoints → Rotate Keys for merchant credentials, or contact SadadSD for bank credentials.

Bank IP Whitelisting

Bank API keys may have an IP whitelist enforced. Requests from non-whitelisted IPs receive 403 Forbidden. Contact SadadSD to update your allowed IP ranges.

Example

curl -X POST https://sadadsd.hithamhaidartech.com/api/bank/resolve \
  -H "X-API-Key: bk_live_a1b2c3d4e5f6..." \
  -H "X-API-Secret: sk_xxxxxxxxxxxxxxxx..." \
  -H "Content-Type: application/json" \
  -d '{"qr_data":"..."}'

Request Format

All write requests (POST) accept a JSON body with Content-Type: application/json. GET parameters are passed as query string parameters.

All endpoints also accept multipart/form-data where file uploads are involved.

Response Format

Every response is JSON with a consistent envelope:

{
  "response": /* endpoint-specific payload, or false on error */,
  "messages": [
    { "message": "Human-readable message", "type": "success" }
  ],
  "time": "2026-08-09 14:23:01"
}
FieldTypeDescription
responseany | falseThe main payload. false on error.
messagesarrayOne or more messages with type: success | error | warning | info.
timedatetimeServer timestamp (Africa/Khartoum timezone, UTC+2).

Error Codes

HTTP StatusMeaning
200Success. Check response for the payload.
400Bad request — missing or invalid parameter. Read messages.
401Authentication failed — invalid or missing API credentials.
403Forbidden — IP not whitelisted, or insufficient role.
404Resource not found.
405Wrong HTTP method for this route.
409Conflict — duplicate resource (e.g. email already registered).
429Too many attempts (OTP retry limit).
500Internal server error.

Transaction States & Transitions

Every SadadSD transaction moves through the following states:

REQUESTED SCANNED SUCCESS
SCANNED FAILED
REQUESTED CANCELLED   SCANNED CANCELLED
REQUESTED EXPIRED  (auto, on expire_at)
StateWho sets itDescription
requestedMerchant APIPayment created, QR generated, waiting to be scanned.
scannedBank API — /resolveBank scanned the QR; payer confirmation screen is being shown.
successBank API — /resultBank confirmed the payment was processed successfully.
failedBank API — /resultBank reported a payment failure (reason provided).
cancelledMerchant API — /cancel_paymentMerchant cancelled the transaction (QR no longer visible).
expiredSadadSD (automatic)Transaction reached expire_at without being completed.
⚠️
Cancellation obligation: Merchants must call POST /cancel_payment whenever the QR is no longer visible to the customer — page navigation, session timeout, POS lock, or explicit cancel. Failure to cancel creates stale transactions that may confuse customers.

QR Code Format

The qr_data returned by POST /create_payment is a Base64-encoded, HMAC-SHA256 signed string with the format:

base64( "<uuid>|<expire_at>|<amount>|<hmac_sha256_signature>" )

The HMAC signature is computed with a server-side secret key — tampered or forged QR codes are rejected by POST /resolve with code: "INVALID_QR".

Encode this string into any QR code format (e.g. QR Code ISO/IEC 18004). For deep-link/inter-app flows pass it as a URL parameter.

Fee Model

SadadSD charges a fixed amount per successful transaction. The payer's confirmation screen must display all three amounts:

FieldDescription
seller_amountAmount due to the merchant (set by merchant when creating the payment).
sadad_feeSadadSD's fixed service fee (set per endpoint, not negotiable per transaction).
total_payableseller_amount + sadad_fee — the total the payer must approve.

All amounts are in SDG (Sudanese Pound).


Bank Integration Guide

Your mobile banking app backend calls the Bank API when a customer wants to pay using a SadadSD QR code.

  1. Customer opens your banking app and initiates payment The customer scans a SadadSD QR code displayed at a merchant checkout (website, POS screen, or printed).
  2. Your app sends the QR data to POST /resolve SadadSD verifies the QR signature, checks expiry, and returns full transaction details: merchant name, amounts, purpose. The transaction is marked scanned.
  3. Show the payer confirmation screen Display merchant_name, seller_amount, sadad_fee, and total_payable. The payer must see and approve all three amounts.
  4. Payer confirms — your backend processes the debit Execute the funds transfer on your side (debit customer account, credit SadadSD partner account).
  5. Report the result to POST /result Submit status: "success" or status: "failed" with your bank_reference number. SadadSD notifies the merchant via webhook.

Bank API Reference

Base URL: https://sadadsd.hithamhaidartech.com/api/bank

All endpoints require X-API-Key and X-API-Secret headers.

POST /resolve

Verify a QR code and retrieve full transaction details for the payer confirmation screen. Marks the transaction as scanned.

Request Body

ParameterTypeDescription
qr_datarequiredstringBase64-encoded QR string from the QR code or deep-link.

Response

{
  "transaction_uuid": "550e8400-e29b-41d4-a716-446655440000",
  "merchant_name":    "Khartoum Electronics",
  "merchant_logo":    "https://...",
  "payment_purpose":  "Product Sale",
  "endpoint_name":   "Main Store",
  "endpoint_type":   "ecommerce",
  "seller_amount":   500.00,
  "sadad_fee":      10.00,
  "total_payable":  510.00,
  "currency":       "SDG",
  "expire_at":      "2026-08-09 14:38:00",
  "requested_at":   "2026-08-09 14:23:00"
}

Error Codes

codeMeaning
INVALID_QRSignature verification failed — QR was tampered or is malformed.
EXPIREDQR code has passed its expiry time.
SCANNED / SUCCESS etc.Transaction is no longer in requested state.

Example

curl -X POST https://sadadsd.hithamhaidartech.com/api/bank/resolve \
  -H "X-API-Key: YOUR_BANK_API_KEY" \
  -H "X-API-Secret: YOUR_BANK_API_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"qr_data":"dXVpZHxleHBpcmV8YW1vdW50fHNpZw=="}'
POST /result

Report the final payment outcome. Only the bank that called /resolve for a transaction may report its result. This endpoint is idempotent — re-submitting the same result returns the stored status.

🔒
Only the bank that scanned the QR (called /resolve) can submit a result for that transaction. Attempts by other banks return 403.

Request Body

ParameterTypeDescription
transaction_uuidrequireduuidThe UUID returned by /resolve.
statusrequiredstring"success" or "failed".
reasonoptionalstringHuman-readable reason, especially for failures (e.g. "Insufficient balance").
bank_referenceoptionalstringYour bank's internal transaction reference number.

Response

{ "transaction_uuid": "550e8400...", "status": "success" }

Example

curl -X POST https://sadadsd.hithamhaidartech.com/api/bank/result \
  -H "X-API-Key: YOUR_BANK_API_KEY" \
  -H "X-API-Secret: YOUR_BANK_API_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"transaction_uuid":"550e8400-...","status":"success","bank_reference":"TXN-2026-88812"}'
GET /transaction

Re-fetch status and amounts for a transaction that was previously scanned by your bank. Useful for reconciliation or re-sync after a connectivity issue.

Query Parameters

ParameterTypeDescription
uuidrequireduuidThe transaction UUID.

Example

curl "https://sadadsd.hithamhaidartech.com/api/bank/transaction?uuid=550e8400-..." \
  -H "X-API-Key: YOUR_BANK_API_KEY" \
  -H "X-API-Secret: YOUR_BANK_API_SECRET"

Merchant Integration Guide

Your server-side backend uses the Merchant API. Each registered endpoint (website, POS device, or app) has its own api_key + api_secret.

  1. Register and get approved via the Portal Create your merchant account at sadadsd.hithamhaidartech.com/portal, submit for review, and add your bank settlement account. Once approved, create an Endpoint to receive your API credentials.
  2. Create a payment with POST /create_payment Send the amount (seller's amount in SDG). Receive a qr_data string and expiry.
  3. Display the QR / launch the banking app Encode qr_data as a QR code and display it. For inter-app flow, pass it as a deep-link parameter to the banking app.
  4. Poll GET /payment_status or receive a webhook Poll every 2–3 seconds until status changes from requested. Or use webhooks for server push.
  5. Cancel if the QR is abandoned Call POST /cancel_payment when the QR is no longer visible. Never leave a requested transaction unresolved.

Merchant API Reference

Base URL: https://sadadsd.hithamhaidartech.com/api/merchant

All endpoints require X-API-Key and X-API-Secret headers for the specific endpoint (not the merchant account). Endpoints must have status active and their parent merchant must be approved.

POST /create_payment

Create a new payment intent. Returns a signed QR string and amount breakdown.

Request Body

ParameterTypeDescription
amountrequirednumberSeller amount in SDG. Must be positive. Does not include the SadadSD fee.
purpose_idoptionalintegerID from GET /purposes. Labels the transaction.
operator_idoptionalintegerID from GET /operators. Identifies the POS cashier.
expire_minutesoptionalintegerQR validity in minutes. Range: 1–1440. Default: 15.
reference_internal_idoptionalintegerYour own order / invoice ID for reconciliation.
reference_internal_tableoptionalstringTable name the reference_internal_id belongs to (e.g. "orders").

Response

{
  "transaction_uuid": "550e8400-e29b-41d4-a716-446655440000",
  "qr_data":         "dXVpZHxleHBpcmV8YW1vdW50fHNpZw==",  // encode this as QR
  "seller_amount":   500.00,
  "sadad_fee":      10.00,
  "total_payable":  510.00,
  "currency":       "SDG",
  "expire_at":      "2026-08-09 14:38:00",
  "expire_minutes": 15
}

Example

curl -X POST https://sadadsd.hithamhaidartech.com/api/merchant/create_payment \
  -H "X-API-Key: YOUR_ENDPOINT_API_KEY" \
  -H "X-API-Secret: YOUR_ENDPOINT_API_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"amount":500.00,"expire_minutes":10,"reference_internal_id":9001}'
GET /payment_status

Poll the current status of a transaction. Also auto-expires overdue requested transactions.

Query Parameters

ParameterTypeDescription
uuidrequireduuidTransaction UUID from /create_payment.

Response

{
  "transaction_uuid": "550e8400-...",
  "status":           "success",
  "status_reason":   "",
  "seller_amount":   500.00,
  "sadad_fee":      10.00,
  "total_payable":  510.00,
  "currency":       "SDG",
  "scanned_by_bank": "Bank of Khartoum",
  "bank_reference":  "TXN-2026-88812",
  "expire_at":       "2026-08-09 14:38:00",
  "requested_at":    "2026-08-09 14:23:00",
  "scanned_at":      "2026-08-09 14:24:12",
  "updated_at":      "2026-08-09 14:25:03"
}
💡
Recommended polling interval: every 2–3 seconds while the QR is displayed. Stop polling when status is success, failed, cancelled, or expired.
POST /cancel_payment

Cancel a pending transaction. Only cancellable while status is requested or scanned. This endpoint is idempotent.

Request Body

ParameterTypeDescription
uuidrequireduuidTransaction UUID.
reasonoptionalstringReason for cancellation. Default: "Cancelled by merchant".

Example

curl -X POST https://sadadsd.hithamhaidartech.com/api/merchant/cancel_payment \
  -H "X-API-Key: YOUR_ENDPOINT_API_KEY" \
  -H "X-API-Secret: YOUR_ENDPOINT_API_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"uuid":"550e8400-...","reason":"Customer closed checkout"}'
POST /register_webhook

Register or update the webhook URL for this endpoint. SadadSD will POST transaction.success and transaction.failed events here. Send an empty webhook_url to remove the webhook.

Request Body

ParameterTypeDescription
webhook_urloptionalurlHTTPS URL to receive events. Must resolve to a public IP.
webhook_secretoptionalstring16–128 character secret for signature verification.
GET /transactions

Paginated list of transactions for this endpoint.

Query Parameters

ParameterTypeDefaultDescription
statusoptionalstringallFilter by status: requested | scanned | success | failed | cancelled | expired
pageoptionalinteger1Page number.
limitoptionalinteger20Records per page. Max: 100.

Response

{ "page": 1, "limit": 20, "total": 143, "transactions": [ /* array of transaction objects */ ] }
GET/me

Returns the authenticated endpoint's configuration and its parent merchant's profile.

GET/operators

Returns active cashier operators for this merchant. Use to populate an operator selector in POS/cashier apps. Pass the selected operator_id to /create_payment.

GET/purposes

Returns active payment purposes. Pass the selected purpose_id to /create_payment to label the transaction.


Webhooks

When a transaction reaches a terminal state, SadadSD sends an HTTP POST to your registered webhook_url.

Events

EventTriggered when
transaction.successBank reports status: "success".
transaction.failedBank reports status: "failed".

Payload

{
  "event":   "transaction.success",
  "data": {
    "transaction_uuid": "550e8400-...",
    "status":           "success",
    "reason":           "",
    "bank_reference":  "TXN-2026-88812",
    "seller_amount":   500.00,
    "sadad_fee":      10.00,
    "total_payable":  510.00
  },
  "timestamp": 1754749381
}

Delivery

  • Timeout: 10 seconds. No automatic retry — use GET /payment_status as a fallback.
  • Your endpoint must return a 2xx response.
  • Verify the signature before trusting the payload (see below).

Webhook Signature Verification

Every webhook delivery includes a X-SadadSD-Signature header. Verify it before processing the event.

X-SadadSD-Signature: sha256=a1b2c3d4e5f6...
X-SadadSD-Event: transaction.success

Verify by computing HMAC-SHA256 of the raw request body using your webhook_secret:

// PHP
$body      = file_get_contents('php://input');
$secret    = 'your_webhook_secret';
$expected  = 'sha256=' . hash_hmac('sha256', $body, $secret);
$received  = $_SERVER['HTTP_X_SADADSD_SIGNATURE'] ?? '';

if (!hash_equals($expected, $received)) {
    http_response_code(401);
    exit('Invalid signature');
}
$event = json_decode($body, true);
🔒
Always use a timing-safe comparison (hash_equals / timingSafeEqual / hmac.compare_digest) to prevent timing attacks.