Appearance
Callbacks and webhooks
Give Mamlaka these two addresses. Both are public HTTPS endpoints, and the exchange checks the provider's own record before acting on either.
| For | Address |
|---|---|
| Payment gateway callbacks | https://apiexchange.yeshara.network/api/v1/hooks/mamlaka/payments |
| Virtual card webhooks | https://apiexchange.yeshara.network/api/v1/hooks/mamlaka/cards |
The gateway address is also sent as callbackUrl on every gateway request, so a merchant set up without a default callback still reaches it.
Signatures
Both endpoints check the same signature, over the raw request body exactly as sent:
X-Mamlaka-Signature: sha256=<hex HMAC-SHA256 of the raw body, keyed with the signing secret>The comparison is constant-time. Re-serialising the JSON before signing (a different key order, different whitespace) produces a different signature, so sign the bytes you send.
| Endpoint | With a signing secret set | Without one |
|---|---|---|
| Payments | A wrong or missing signature is refused with 401 bad_signature | Accepted, but only used as a prompt to look up the transaction |
| Cards | A wrong or missing signature is refused with 401 bad_signature | Every webhook is refused; the card webhook requires a secret |
Payment gateway callback
The exchange reads one field from the body: externalId, which is the key it sent with the payment. It then calls GET /transaction?reference=<externalId> and settles the payment on that answer:
| Gateway status | The payment becomes |
|---|---|
COMPLETE | Confirmed, and what it was for is written: the invoice payment, the funded escrow, the payout or advance |
FAILED | Failed, with the gateway's reason |
PENDING, or no answer | Left open; the poller asks again |
Answer: always 200, unless the signature is wrong.
json
{ "ok": true, "acted": true, "transfer": "TRF-0042" }acted: false means the callback named no payment the exchange is waiting on: an unknown key, or one already settled. Sending the same callback twice is harmless.
Card webhook
Event type | What the exchange does |
|---|---|
kyc.status_changed | Reads the cardholder's KYC decision back from the Partner API |
card.updated | Reads the driver's cards back: status, balance |
operation.updated | Reads the issue or load back by its data.reference and settles it |
Each event is acted on once. The event id is taken from the X-Mamlaka-Event-Id header, or the body's id; a repeat is answered { "ok": true, "acted": false, "duplicate": true }.
If a callback never arrives
Nothing waits on one. A poller in the exchange checks every open payment on a short interval (10 seconds by default), and Finance can run a check at once with Check the network now on Finance → Transfers. A payment confirmed by the poller and then by a late callback is settled once.