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.
| Notification | Method | Path |
|---|---|---|
| Deposits | POST | {affiliate_base_url}/notifications/apuesteria/deposits/ |
| Withdrawals | POST | {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.
| Field | Type | Description |
|---|---|---|
status | string | Outcome of the transaction on our side (e.g. success). |
code | string | Result code for the transaction (e.g. 0000). |
point_of_sale.id | integer | Identifier of the point of sale the transaction belongs to. |
point_of_sale.name | string | Human-readable name of the point of sale. |
point_of_sale.currency_code | string | Currency of the point of sale (ISO 4217, e.g. MXN). |
deposit / withdrawal.username | string | The end user's username for the transaction. |
deposit / withdrawal.description | string | Human-readable description of the transaction. |
deposit / withdrawal.transaction_number | string | Unique identifier of the transaction. Use it as your idempotency key. |
deposit / withdrawal.amount | number | Transaction amount. |
deposit / withdrawal.currency_code | string | Currency of the transaction (ISO 4217, e.g. MXN). |
created_at | string | Timestamp 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 return201 Created. - Any later notification carrying a
transaction_numberyou have already recorded → do not apply it again and return409 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_numberfollows one rule: echo the exact value we sent whenever you could read it; otherwise sendnull. There are only two cases where you could not read it — you rejected the request before parsing the body (a bad signature), or thetransaction_numberfield 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.messagemust 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.
| Status | When | Body |
|---|---|---|
400 Bad Request | Invalid or empty payload | {"status":"error","transaction_number":null,"message":"JSON data is empty"} |
401 Unauthorized / 403 Forbidden | Signature validation failed | {"status":"error","transaction_number":null,"message":"Invalid signature"} |
404 Not Found | Point of sale not found | {"status":"error","transaction_number":"4345FF2XB7F323CD","message":"Point of sale ID is not found"} |
409 Conflict | Duplicate transaction_number | {"status":"error","transaction_number":"4345FF2XB7F323CD","message":"Transaction already processed"} |
500 Internal Server Error | Unexpected 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.