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).
Bank Integration
Resolve QR codes, show payer confirmation, report payment result.
Merchant Integration
Create payments, display QR, poll status, receive webhooks.
Base URLs
| API | Base URL |
|---|---|
| Bank API | https://sadadsd.hithamhaidartech.com/api/bank |
| Merchant API | https://sadadsd.hithamhaidartech.com/api/merchant |
| Portal API | https://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.
| Header | Value | Description |
|---|---|---|
X-API-Key | string | Your public API key — identifies your integration. |
X-API-Secret | string | Your secret — verified server-side with bcrypt. Treat like a password. |
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"
}
| Field | Type | Description |
|---|---|---|
response | any | false | The main payload. false on error. |
messages | array | One or more messages with type: success | error | warning | info. |
time | datetime | Server timestamp (Africa/Khartoum timezone, UTC+2). |
Error Codes
| HTTP Status | Meaning |
|---|---|
200 | Success. Check response for the payload. |
400 | Bad request — missing or invalid parameter. Read messages. |
401 | Authentication failed — invalid or missing API credentials. |
403 | Forbidden — IP not whitelisted, or insufficient role. |
404 | Resource not found. |
405 | Wrong HTTP method for this route. |
409 | Conflict — duplicate resource (e.g. email already registered). |
429 | Too many attempts (OTP retry limit). |
500 | Internal server error. |
Transaction States & Transitions
Every SadadSD transaction moves through the following states:
| State | Who sets it | Description |
|---|---|---|
requested | Merchant API | Payment created, QR generated, waiting to be scanned. |
scanned | Bank API — /resolve | Bank scanned the QR; payer confirmation screen is being shown. |
success | Bank API — /result | Bank confirmed the payment was processed successfully. |
failed | Bank API — /result | Bank reported a payment failure (reason provided). |
cancelled | Merchant API — /cancel_payment | Merchant cancelled the transaction (QR no longer visible). |
expired | SadadSD (automatic) | Transaction reached expire_at without being completed. |
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:
| Field | Description |
|---|---|
seller_amount | Amount due to the merchant (set by merchant when creating the payment). |
sadad_fee | SadadSD's fixed service fee (set per endpoint, not negotiable per transaction). |
total_payable | seller_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.
- 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).
-
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. -
Show the payer confirmation screen
Display
merchant_name,seller_amount,sadad_fee, andtotal_payable. The payer must see and approve all three amounts. - Payer confirms — your backend processes the debit Execute the funds transfer on your side (debit customer account, credit SadadSD partner account).
-
Report the result to POST /result
Submit
status: "success"orstatus: "failed"with yourbank_referencenumber. 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.
Verify a QR code and retrieve full transaction details for the payer confirmation screen. Marks the transaction as scanned.
Request Body
| Parameter | Type | Description |
|---|---|---|
| qr_datarequired | string | Base64-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
| code | Meaning |
|---|---|
INVALID_QR | Signature verification failed — QR was tampered or is malformed. |
EXPIRED | QR 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=="}'
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.
/resolve) can submit a result for that transaction. Attempts by other banks return 403.Request Body
| Parameter | Type | Description |
|---|---|---|
| transaction_uuidrequired | uuid | The UUID returned by /resolve. |
| statusrequired | string | "success" or "failed". |
| reasonoptional | string | Human-readable reason, especially for failures (e.g. "Insufficient balance"). |
| bank_referenceoptional | string | Your 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"}'
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
| Parameter | Type | Description |
|---|---|---|
| uuidrequired | uuid | The 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.
- 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.
-
Create a payment with POST /create_payment
Send the
amount(seller's amount in SDG). Receive aqr_datastring and expiry. -
Display the QR / launch the banking app
Encode
qr_dataas a QR code and display it. For inter-app flow, pass it as a deep-link parameter to the banking app. -
Poll GET /payment_status or receive a webhook
Poll every 2–3 seconds until status changes from
requested. Or use webhooks for server push. -
Cancel if the QR is abandoned
Call POST /cancel_payment when the QR is no longer visible. Never leave a
requestedtransaction 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.
Create a new payment intent. Returns a signed QR string and amount breakdown.
Request Body
| Parameter | Type | Description |
|---|---|---|
| amountrequired | number | Seller amount in SDG. Must be positive. Does not include the SadadSD fee. |
| purpose_idoptional | integer | ID from GET /purposes. Labels the transaction. |
| operator_idoptional | integer | ID from GET /operators. Identifies the POS cashier. |
| expire_minutesoptional | integer | QR validity in minutes. Range: 1–1440. Default: 15. |
| reference_internal_idoptional | integer | Your own order / invoice ID for reconciliation. |
| reference_internal_tableoptional | string | Table 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}'
Poll the current status of a transaction. Also auto-expires overdue requested transactions.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
| uuidrequired | uuid | Transaction 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"
}
success, failed, cancelled, or expired.Cancel a pending transaction. Only cancellable while status is requested or scanned. This endpoint is idempotent.
Request Body
| Parameter | Type | Description |
|---|---|---|
| uuidrequired | uuid | Transaction UUID. |
| reasonoptional | string | Reason 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"}'
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
| Parameter | Type | Description |
|---|---|---|
| webhook_urloptional | url | HTTPS URL to receive events. Must resolve to a public IP. |
| webhook_secretoptional | string | 16–128 character secret for signature verification. |
Paginated list of transactions for this endpoint.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| statusoptional | string | all | Filter by status: requested | scanned | success | failed | cancelled | expired |
| pageoptional | integer | 1 | Page number. |
| limitoptional | integer | 20 | Records per page. Max: 100. |
Response
{ "page": 1, "limit": 20, "total": 143, "transactions": [ /* array of transaction objects */ ] }
Returns the authenticated endpoint's configuration and its parent merchant's profile.
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.
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
| Event | Triggered when |
|---|---|
transaction.success | Bank reports status: "success". |
transaction.failed | Bank 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
2xxresponse. - 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);
// Node.js
const crypto = require('crypto');
function verify(req, secret) {
const body = req.rawBody; // ensure you have raw body
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(body)
.digest('hex');
const received = req.headers['x-sadadsd-signature'] ?? '';
return crypto.timingSafeEqual(
Buffer.from(expected), Buffer.from(received)
);
}
# Python
import hmac, hashlib
def verify(body: bytes, secret: str, header: str) -> bool:
expected = 'sha256=' + hmac.new(
secret.encode(), body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, header)
hash_equals / timingSafeEqual / hmac.compare_digest) to prevent timing attacks.