Overview
You can receive payout status change notifications at a URL you provide. When a payout’s status changes, we send an HTTP POST to your endpoint with the event payload. We use five consolidated statuses: PENDING, PROCESSING, SUCCESSFUL, FAILED, and REJECTED.
The same webhook URL also receives wallet credit and debit notifications for your business (see Wallet credit and debit). You do not register a second URL.
- One webhook URL per business — you can register a single HTTPS endpoint; we send payout status events and wallet movement events for your business to that URL.
- Optional signing — you can provide a secret when registering; we then sign each request so you can verify it came from Yala. We strongly recommend setting a secret and verifying the signature to protect your endpoint against spoofed webhooks.
- Use the same API key (and base URL) as for the rest of the Payout API when managing your webhook subscription.
Subscribe via API
Use the Payout API with your existing x-api-key to manage your webhook URL.
Base path: Same as payouts — https://gateway.staging.useyala.com/v1/payout-api (sandbox) or https://gateway.useyala.com/v1/payout-api (production). Webhook endpoints live under /webhooks.
| Action | Method | Path |
|---|
| Register URL | POST | /webhooks |
| List subscription | GET | /webhooks |
| Get one | GET | /webhooks/:id |
| Update URL or secret | PATCH | /webhooks/:id |
| Remove | DELETE | /webhooks/:id |
Register a webhook URL
POST /v1/payout-api/webhooks HTTP/1.1
Host: gateway.staging.useyala.com
Content-Type: application/json
x-api-key: <YOUR_API_KEY>
Request body:
| Field | Required | Description |
|---|
url | Yes | Your HTTPS endpoint. We POST the event payload to this URL. |
secret | No (recommended) | Shared secret used to sign requests. If set, we send X-Yala-Signature so you can verify the payload. Recommended to protect against spoofed webhooks. |
eventTypes | No | Filters which payout status event types you receive. Omit or leave empty to receive all payout status events. Use payout.status.changed (recommended) or legacy payout.status.changed.v1; both match the same notifications. Wallet credit and debit notifications are always sent to your URL for your business; they are not filtered by this field. |
Example:
{
"url": "https://your-server.com/webhooks/payout",
"secret": "whsec_your_secret_for_signing"
}
Only one active webhook URL is allowed per business. To change it, update the existing subscription (PATCH) or delete and create a new one.
Payload we send
Each notification is an HTTP POST with:
- Content-Type:
application/json
- Body: JSON object with the shape below.
- Headers we add:
X-Yala-Delivery-Id — unique ID for this delivery (use it to deduplicate if you receive the same event more than once).
X-Yala-Signature — only present if you set a secret; format sha256=<hex>. See Verifying the signature.
Payload shape
{
"eventType": "payout.status.changed",
"deliveryId": "550e8400-e29b-41d4-a716-446655440000",
"occurredAt": "2025-02-10T12:00:00.000Z",
"data": {
"payoutId": "payout-uuid",
"businessId": "business-uuid",
"oldStatus": "PENDING",
"newStatus": "PROCESSING",
"changedAt": "2025-02-10T12:00:00.000Z",
"reason": "Approved for processing"
}
}
Status values — We send exactly five statuses so you can integrate simply:
| Status | Meaning |
|---|
PENDING | Payout created or awaiting approval; not yet sent for processing. |
PROCESSING | Payout is being processed (ops, bank, or treasury). |
SUCCESSFUL | Completed; funds have been sent. |
FAILED | Processing failed (e.g. bank failure). |
REJECTED | Rejected (e.g. compliance, initiation checks, or bank). |
| Field | Description |
|---|
eventType | Event name in the POST body: payout.status.changed. (You may still use payout.status.changed.v1 in eventTypes when registering; it matches the same deliveries.) |
deliveryId | Unique ID for this delivery. Use for idempotency (e.g. ignore duplicates with the same deliveryId). |
occurredAt | When the status change occurred (ISO 8601). |
data.payoutId | Payout ID (same as the id returned from Initiate Payout). |
data.businessId | Your business ID. |
data.oldStatus | Previous status (one of the five above). |
data.newStatus | New status (one of the five above). |
data.changedAt | Timestamp of the change (ISO 8601). |
data.reason | Optional; human-readable reason. |
Wallet credit and debit
When a wallet for your business is credited or debited, we POST to the same registered URL as payout webhooks. These messages are separate from payout status updates—check eventType and data.transactionType to route them in your handler.
Event names (eventType)
eventType | When |
|---|
wallet.credited | Funds were credited to a wallet (e.g. pay-in, top-up). |
wallet.debited | Funds were debited from a wallet (e.g. payout, fee). |
Direction (transactionType)
Use data.transactionType so you do not rely only on eventType:
| Value | Meaning |
|---|
cr | Credit (money in). |
dr | Debit (money out). |
Example payload (wallet)
{
"eventType": "wallet.credited",
"deliveryId": "550e8400-e29b-41d4-a716-446655440000",
"occurredAt": "2025-02-10T12:00:00.000Z",
"data": {
"businessId": "business-uuid",
"amount": 1000.0,
"currency": "USD",
"transactionId": "transaction-uuid",
"createdAt": "2025-02-10T12:00:00.000Z",
"transactionType": "cr",
"reference": "REF-123",
"narration": "wallet top up resolution - funding",
"balance": 5000.0,
"totalBalance": 5200.0
}
}
Wallet data fields
| Field | Description |
|---|
businessId | Your business ID. |
amount | Amount of this movement (always positive). |
currency | Wallet currency. |
transactionId | Identifier for this wallet transaction. |
createdAt | When the transaction was recorded (ISO 8601). |
transactionType | cr (credit) or dr (debit). |
reference | Stable reference for the movement (e.g. payout ref, batch id). Empty string if none. |
narration | Human-readable narration (e.g. wallet top-up funding resolution, payout description). Empty string if none. |
balance | Available balance after this transaction. It does not include funds held for pending operations (e.g. pending payouts). |
totalBalance | Total balance including active holds for pending transactions (available balance plus amounts held). |
Headers (X-Yala-Delivery-Id, X-Yala-Signature), retries, and verification work the same as for payout payloads.
Verifying the signature
If you registered a secret, we sign each request with HMAC-SHA256 of the raw request body (the exact JSON string we send). We send the hex-encoded digest in the header:
X-Yala-Signature: sha256=a1b2c3...
How to verify:
- Read the raw request body (do not parse and re-serialize; use the exact bytes received).
- Compute
HMAC-SHA256(rawBody, yourSecret).
- Hex-encode the result.
- Compare with the value in
X-Yala-Signature after the sha256= prefix.
If they match, the request came from Yala and the body was not modified in transit.
Use the raw body. Small differences in JSON formatting (e.g. key order or spacing) will change the signature. Always verify against the exact bytes you receive.
Best practices
- Respond quickly — Return HTTP
2xx as soon as you have accepted the payload. Process the event asynchronously if needed so we don’t time out.
- Deduplicate — Store
deliveryId and ignore requests you have already processed so duplicate deliveries do not trigger duplicate actions.
- Set a secret and verify the signature — We strongly recommend setting a secret when registering and always verifying
X-Yala-Signature before trusting the payload. Without this, your endpoint could accept spoofed webhooks from third parties.
- Use HTTPS only — We only send to HTTPS URLs. Keep your endpoint and secret secure.
Delivery retries
If your endpoint does not return HTTP 2xx, we retry delivery automatically.
- Total attempts: 5
- Backoff schedule: 1 minute, 5 minutes, 15 minutes, 1 hour, 4 hours
- Stop condition: Any HTTP
2xx response marks the delivery as successful and stops retries
- Final state: After the 5th failed attempt, the delivery is marked as failed
Managing your subscription
- List:
GET /v1/payout-api/webhooks returns your current subscription(s). The response does not include the secret.
- Update:
PATCH /v1/payout-api/webhooks/:id with url, secret, eventTypes, or isActive as needed.
- Remove:
DELETE /v1/payout-api/webhooks/:id stops notifications to that URL.
All of these use the same authentication as the rest of the Payout API (x-api-key).
You can also manage your webhook URL from the Yala app under Settings → Payout API Keys (webhook section). The API and the app stay in sync.