Skip to main content

Step 2 · Webhooks

We notify you of every deposit and withdrawal by sending a webhook — an HTTP POST to two endpoints you host. Your platform receives the notification, validates the signature, records the transaction, and replies with one of the documented responses below.

Each request carries an Authorization: Bearer <signature> header. Always validate the signature before trusting the payload.

The two endpoints

You host these two endpoints on your own domain. Both are POST with Content-Type: application/json and the Authorization: Bearer <signature> header.

NotificationMethodPath
DepositsPOST{affiliate_base_url}/notifications/apuesteria/deposits/
WithdrawalsPOST{affiliate_base_url}/notifications/apuesteria/withdrawals/

{affiliate_base_url} is the base URL you provide for your integration.

Deposit notification

This is the request body we send to your deposits endpoint:

{
"status": "success",
"code": "0000",
"point_of_sale": {
"id": 124,
"name": "Sucursal de Pruebas",
"currency_code": "MXN"
},
"deposit": {
"username": "5555555555",
"description": "Deposito en Cuenta - 4345FF2XB7F323CD",
"transaction_number": "4345FF2XB7F323CD",
"amount": 100,
"currency_code": "MXN"
},
"created_at": "2019-05-18 13:18:37"
}

Withdrawal notification

This is the request body we send to your withdrawals endpoint:

{
"status": "success",
"code": "0000",
"point_of_sale": {
"id": 123,
"name": "Sucursal de Pruebas",
"currency_code": "MXN"
},
"withdrawal": {
"username": "5555555555",
"description": "Retiro de Cuenta - 4345FF2XB7F3123D",
"transaction_number": "4345FF2XB7F3123D",
"amount": 200,
"currency_code": "MXN"
},
"created_at": "2019-05-18 13:18:37"
}

Field reference

Both notifications share the same shape. The only difference is the transaction object, which is named deposit in a deposit notification and withdrawal in a withdrawal notification; its inner fields are identical.

FieldTypeDescription
statusstringOutcome of the transaction on our side (e.g. success).
codestringResult code for the transaction (e.g. 0000).
point_of_sale.idintegerIdentifier of the point of sale the transaction belongs to.
point_of_sale.namestringHuman-readable name of the point of sale.
point_of_sale.currency_codestringCurrency of the point of sale (ISO 4217, e.g. MXN).
deposit / withdrawal.usernamestringThe end user's username for the transaction.
deposit / withdrawal.descriptionstringHuman-readable description of the transaction.
deposit / withdrawal.transaction_numberstringUnique identifier of the transaction. Use it as your idempotency key.
deposit / withdrawal.amountnumberTransaction amount.
deposit / withdrawal.currency_codestringCurrency of the transaction (ISO 4217, e.g. MXN).
created_atstringTimestamp of the transaction (YYYY-MM-DD HH:MM:SS).

transaction_number uniquely identifies the transaction. Use it as your idempotency key: look the transaction up by transaction_number before recording it, and never apply the same transaction twice.

Idempotency: duplicate notifications

Your endpoint must be idempotent, keyed on transaction_number. Although we send each notification once and do not retry, the same transaction_number could still reach you more than once — for example if it is repeated on our side by mistake — so deduplicate so that a repeated event is never processed (for example, credited) twice.

  • The first notification for a given transaction_number → record it and return 201 Created.
  • Any later notification carrying a transaction_number you have already recorded → do not apply it again and return 409 Conflict.

Distinguishing the two with different status codes (201 for new, 409 for a duplicate) makes your idempotency observable, and it is exactly what the Webhook Simulator verifies before you go live.

Expected response

Your endpoint must reply with one of the responses below. On success, return 201 Created with the transaction_number you received:

{
"status": "success",
"transaction_number": "4345FF2XB7F323CD",
"message": null
}

status must be "success" and transaction_number must echo the value we sent. message is optional here — it may be null, absent, or an empty string.

Error responses

Every non-success response — both 4xx and 5xx — must use this exact JSON body shape:

{
"status": "error",
"transaction_number": "4345FF2XB7F323CD",
"message": "Human-readable description of the problem"
}
  • transaction_number follows one rule: echo the exact value we sent whenever you could read it; otherwise send null. There are only two cases where you could not read it — you rejected the request before parsing the body (a bad signature), or the transaction_number field was not in the payload we sent. In every other case, echo it, so we can correlate the failure to that precise transaction in our logs.
  • message must be a non-empty string describing what went wrong.

This shape is uniform across every error, including the rejections documented elsewhere: an invalid payload (400) and a bad signature (401 / 403) return this same body — it is not only for 5xx.

StatusWhenBody
400 Bad RequestInvalid or empty payload{"status":"error","transaction_number":null,"message":"JSON data is empty"}
401 Unauthorized / 403 ForbiddenSignature validation failed{"status":"error","transaction_number":null,"message":"Invalid signature"}
404 Not FoundPoint of sale not found{"status":"error","transaction_number":"4345FF2XB7F323CD","message":"Point of sale ID is not found"}
409 ConflictDuplicate transaction_number{"status":"error","transaction_number":"4345FF2XB7F323CD","message":"Transaction already processed"}
500 Internal Server ErrorUnexpected error on your side{"status":"error","transaction_number":"4345FF2XB7F323CD","message":"Internal Server Error"}

When you reject an invalid payload (400) but the transaction_number was present in what we sent, echo that value rather than null, so the failure still correlates. 409 Conflict is the response to a duplicate transaction_number you have already recorded — see Idempotency above.

Timing and reliability

We wait up to 10 seconds for your endpoint to respond. Respond promptly — do the minimal work needed to record the transaction and return one of the documented responses, and move any slower processing off the request path. Keep your endpoint robust: if something fails on your side, still return the documented error body (for example 500 Internal Server Error) rather than letting the request hang until it times out.

We send each notification once and do not retry it. If your endpoint fails or does not respond within those 10 seconds, the event is not resent — so your endpoint must be robust and respond promptly to avoid missing notifications.

Validate the signature first

Every notification carries an Authorization: Bearer <signature> header, and your endpoint must validate it before trusting the payload. The JSON on this page is formatted for readability; the signature is computed over the exact raw bytes as transmitted, so validate against the raw request body, not a re-serialized version. See Signature for the formula, a worked example, and validation code.

Validate your integration

Once your deposits and withdrawals endpoints are built, use the Webhook Simulator to confirm they behave correctly — valid signatures, rejected tampering, field validation, and 201/409 idempotency — against real signed requests. Passing every scenario there is the validation step before you launch the system.