Skip to main content

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​

SUCCEEDEDfinal

The payout succeeded and a webhook is sent.

FAILEDfinal

The 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-Digest

sha-256=:base64(SHA-256(rawBody)): over the exact body bytes (RFC 9530).

Webhook-Event-Id

Event id. Always equals eventId in the body.

Signature-Input

Label 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.

Signature

platform=: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"
  • @path and @query come from your webhookUrl as received. @query is ? plus the raw query; it is ? when the URL has no query string.
  • Lines are joined with \n without a trailing newline. The last line is the Signature-Input value without the platform= 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​

  1. Read the raw request

    Preserve the exact body bytes, the request path, and the raw query string for verification.

  2. Verify the signature

    Recompute Content-Digest, check that Webhook-Event-Id equals body eventId, validate the time window and nonce, select the platform public key by keyid, and verify Signature over the reconstructed signature base. Reject on any failure without parsing further.

  3. Store idempotently

    Use eventId as the delivery idempotency key, and apply status updates idempotently per orderNo or merchantOrderNo.

  4. 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.