Payout webhook
Pass webhookUrl when creating a payout order. When the order enters a merchant-actionable final state, DEEPayment sends a plain JSON webhook to that URL, signed with the platform Ed25519 key (RFC 9421). The body is not encrypted.
Payout webhooks only deliver final states. Verify the signature before updating payout results, and use the payout query API when callbacks are duplicated, delayed, or conflicting. The SDK's parsePayoutWebhook performs every check below.
Delivered Statuses
SUCCEEDEDfinalThe payout succeeded and a webhook is sent.
FAILEDfinalThe payout failed and a webhook is sent.
REFUNDED is sent when a previously successful payout is returned upstream and the merchant refund has been credited. It uses the same payout URL and signature protocol, with a new event ID separate from the original success event.
PENDING and PROCESSING are visible only in query APIs and do not trigger webhooks.
HTTP Headers
POST /webhook/payout HTTP/1.1
Content-Type: application/json
Content-Digest: sha-256=:base64-sha256-of-body:
Webhook-Event-Id: evt_0123456789ABCDEFGHJKMNPQRT
Signature-Input: platform=("@method" "@path" "@query" "content-type" "content-digest" "webhook-event-id");created=1787803200;expires=1787803500;nonce="b7754a6c-4a9c-4cf0-b77f-6f2d4b7e5f5a";keyid="pwhk_20260827_01";alg="ed25519"
Signature: platform=:base64-ed25519-signature:
Content-Digestsha-256=:base64(SHA-256(rawBody)): over the exact body bytes (RFC 9530).
Webhook-Event-IdEvent id. Always equals eventId in the body.
Signature-InputLabel platform. keyid selects the platform webhook public key shown in the merchant portal. created and expires are Unix seconds, at most 300 seconds apart. nonce is a UUID v4.
Signatureplatform=:base64(Ed25519 signature):
Signature Rule
The signature base uses the same RFC 9421 format as API requests. Covered components are fixed:
"@method": POST
"@path": /webhook/payout
"@query": ?
"content-type": application/json
"content-digest": sha-256=:base64-sha256-of-body:
"webhook-event-id": evt_0123456789ABCDEFGHJKMNPQRT
"@signature-params": ("@method" "@path" "@query" "content-type" "content-digest" "webhook-event-id");created=1787803200;expires=1787803500;nonce="b7754a6c-4a9c-4cf0-b77f-6f2d4b7e5f5a";keyid="pwhk_20260827_01";alg="ed25519"
@pathand@querycome from yourwebhookUrlas received.@queryis?plus the raw query; it is?when the URL has no query string.- Lines are joined with
\nwithout a trailing newline. The last line is theSignature-Inputvalue without theplatform=label. - Verify with
Ed25519.Verify(platformWebhookPublicKey[keyid], signatureBase, signature).
Every delivery attempt is signed fresh: retries carry a new created, expires, nonce, and Signature, while Webhook-Event-Id and the body stay identical. Deduplicate by eventId.
Event Example
{
"eventId": "evt_0123456789ABCDEFGHJKMNPQRT",
"orderType": "PAYOUT",
"orderNo": "FO202605060001",
"merchantOrderNo": "P202605060001",
"status": "SUCCEEDED",
"currency": "BRL",
"amount": "100.00",
"channelTradeNo": "E1234567891",
"attach": "withdraw_123"
}
eventId identifies one notification event. Retries of the same event reuse the same eventId.
Payout webhooks do not include sender or receiver identity details. Payout account details, when available, are returned by the payout receipt endpoint.
Verification Example
Payout webhooks use the same headers and signature base as payment webhooks. See the verification examples on the Payment webhook page; only the orderType and body fields differ.
Merchant Processing Order
- Read the raw request
Preserve the exact body bytes, the request path, and the raw query string for verification.
- Verify the signature
Recompute
Content-Digest, check thatWebhook-Event-Idequals bodyeventId, validate the time window and nonce, select the platform public key bykeyid, and verifySignatureover the reconstructed signature base. Reject on any failure without parsing further. - Store idempotently
Use
eventIdas the delivery idempotency key, and apply status updates idempotently perorderNoormerchantOrderNo. - Confirm when needed
When local state conflicts with the event, query the payout order and trust the confirmed final status. Return HTTP 2xx only after processing or durable acceptance into your queue; non-2xx triggers a retry. After acceptance, retry temporary query and processing failures in your own queue.
Payout return
{
"eventId": "evt_00000000000000000000000002",
"orderType": "PAYOUT",
"orderNo": "PO202609240001",
"merchantOrderNo": "M202609240001",
"status": "REFUNDED",
"currency": "MXN",
"amount": "100.00",
"refundNo": "R202609240001",
"refundAmount": "100.00",
"refundTime": 1790208000000
}
refundNo: refund identifier.refundAmount: full returned principal as a decimal string in major units, not the net balance credit after fees.refundTime: refund posting time in Unix milliseconds. It does not change on retry.
The original payout query returns the same REFUNDED result and fields. Before refund posting completes, it retains the original payment result and omits refund fields. Apply a refund only once per original order or refund number; a late success notification must not overwrite a refunded state. The refund event's ID and body stay fixed on retry, even when the original success notification has already completed.
Automatic delivery retries stop on HTTP 2xx or 24 hours after the refund notification is created. The deadline is independent of the original payout's creation time. Failed notification records are retained; merchants can query the payout to confirm the refund after automatic retries stop.
This covers full payout returns, without a new refund-request API. Payment refunds and partial refunds are outside this contract.