Paso 2 · Webhooks
Te notificamos cada depósito y retiro enviando un webhook — una solicitud HTTP POST a dos endpoints que tú alojas. Tu plataforma recibe la notificación, valida la firma, registra la transacción y responde con una de las respuestas documentadas más abajo.
Cada solicitud incluye un header Authorization: Bearer <signature>. Valida siempre la firma antes de confiar en el payload.
Los dos endpoints
Tú alojas estos dos endpoints en tu propio dominio. Ambos son POST con Content-Type: application/json y el header Authorization: Bearer <signature>.
| Notificación | Método | Ruta |
|---|---|---|
| Depósitos | POST | {affiliate_base_url}/notifications/apuesteria/deposits/ |
| Retiros | POST | {affiliate_base_url}/notifications/apuesteria/withdrawals/ |
{affiliate_base_url} es la URL base que nos proporcionas para tu integración.
Notificación de depósito
Este es el cuerpo de la solicitud que enviamos a tu endpoint de depósitos:
{
"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"
}
Notificación de retiro
Este es el cuerpo de la solicitud que enviamos a tu endpoint de retiros:
{
"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"
}
Referencia de campos
Ambas notificaciones comparten la misma estructura. La única diferencia es el objeto de la transacción, que se llama deposit en una notificación de depósito y withdrawal en una de retiro; sus campos internos son idénticos.
| Campo | Tipo | Descripción |
|---|---|---|
status | string | Resultado de la transacción de nuestro lado (p. ej. success). |
code | string | Código de resultado de la transacción (p. ej. 0000). |
point_of_sale.id | integer | Identificador del punto de venta al que pertenece la transacción. |
point_of_sale.name | string | Nombre legible del punto de venta. |
point_of_sale.currency_code | string | Moneda del punto de venta (ISO 4217, p. ej. MXN). |
deposit / withdrawal.username | string | Nombre de usuario del usuario final de la transacción. |
deposit / withdrawal.description | string | Descripción legible de la transacción. |
deposit / withdrawal.transaction_number | string | Identificador único de la transacción. Úsalo como tu clave de idempotencia. |
deposit / withdrawal.amount | number | Monto de la transacción. |
deposit / withdrawal.currency_code | string | Moneda de la transacción (ISO 4217, p. ej. MXN). |
created_at | string | Marca de tiempo de la transacción (YYYY-MM-DD HH:MM:SS). |
transaction_number identifica de forma única la transacción. Úsalo como tu clave de idempotencia: busca la transacción por transaction_number antes de registrarla y nunca apliques la misma transacción dos veces.
Idempotencia: notificaciones duplicadas
Tu endpoint debe ser idempotente, con clave en transaction_number. Aunque enviamos cada notificación una sola vez y no reintentamos, el mismo transaction_number podría llegarte más de una vez — por ejemplo si se repite de nuestro lado por error — así que deduplica para que un evento repetido nunca se procese (por ejemplo, se acredite) dos veces.
- La primera notificación para un
transaction_numberdado → regístrala y devuelve201 Created. - Cualquier notificación posterior que traiga un
transaction_numberque ya registraste → no la apliques de nuevo y devuelve409 Conflict.
Distinguir ambos casos con códigos de estado diferentes (201 para nueva, 409 para duplicada) hace tu idempotencia observable, y es exactamente lo que verifica el Simulador de Webhooks antes de que salgas a producción.
Respuesta esperada
Tu endpoint debe responder con una de las respuestas siguientes. En caso de éxito, devuelve 201 Created con el transaction_number que recibiste:
{
"status": "success",
"transaction_number": "4345FF2XB7F323CD",
"message": null
}
status debe ser "success" y transaction_number debe devolver el valor que enviamos. message es opcional aquí — puede ser null, estar ausente o ser una cadena vacía.
Respuestas de error
Toda respuesta que no sea de éxito — tanto 4xx como 5xx — debe usar exactamente esta forma de cuerpo JSON:
{
"status": "error",
"transaction_number": "4345FF2XB7F323CD",
"message": "Human-readable description of the problem"
}
transaction_numbersigue una sola regla: devuelve el mismo valor exacto que te enviamos siempre que hayas podido leerlo; en caso contrario, envíanull. Solo hay dos casos en los que no pudiste leerlo — rechazaste la solicitud antes de parsear el cuerpo (una firma incorrecta), o el campotransaction_numberno venía en el payload que enviamos. En cualquier otro caso, devuélvelo, para que podamos correlacionar el fallo con esa transacción exacta en nuestros registros.messagedebe ser una cadena no vacía que describa qué salió mal.
Esta forma es uniforme para todos los errores, incluidos los rechazos documentados en otras páginas: un payload inválido (400) y una firma incorrecta (401 / 403) devuelven este mismo cuerpo — no es solo para 5xx.
| Status | Cuándo | Cuerpo |
|---|---|---|
400 Bad Request | Payload inválido o vacío | {"status":"error","transaction_number":null,"message":"JSON data is empty"} |
401 Unauthorized / 403 Forbidden | Falló la validación de firma | {"status":"error","transaction_number":null,"message":"Invalid signature"} |
404 Not Found | Punto de venta no encontrado | {"status":"error","transaction_number":"4345FF2XB7F323CD","message":"Point of sale ID is not found"} |
409 Conflict | transaction_number duplicado | {"status":"error","transaction_number":"4345FF2XB7F323CD","message":"Transaction already processed"} |
500 Internal Server Error | Error inesperado de tu lado | {"status":"error","transaction_number":"4345FF2XB7F323CD","message":"Internal Server Error"} |
Cuando rechaces un payload inválido (400) pero el transaction_number venía presente en lo que te enviamos, devuelve ese valor en lugar de null, para que el fallo siga correlacionando. 409 Conflict es la respuesta a un transaction_number duplicado que ya registraste — consulta Idempotencia más arriba.
Tiempos y fiabilidad
Esperamos hasta 10 segundos a que tu endpoint responda. Responde con prontitud — haz el mínimo trabajo necesario para registrar la transacción y devolver una de las respuestas documentadas, y mueve cualquier procesamiento más lento fuera de la ruta de la solicitud. Mantén tu endpoint robusto: si algo falla de tu lado, aun así devuelve el cuerpo de error documentado (por ejemplo 500 Internal Server Error) en lugar de dejar que la solicitud se cuelgue hasta agotar el tiempo.
Enviamos cada notificación una sola vez y no la reintentamos. Si tu endpoint falla o no responde dentro de esos 10 segundos, el evento no se reenvía — así que tu endpoint debe ser robusto y responder con prontitud para no perder notificaciones.
Valida primero la firma
Cada notificación incluye un header Authorization: Bearer <signature>, y tu endpoint debe validarlo antes de confiar en el payload. El JSON de esta página está formateado para facilitar la lectura; la firma se calcula sobre los bytes crudos exactos tal como se transmiten, así que valida contra el cuerpo crudo de la solicitud, no contra una versión re-serializada. Consulta Firma para la fórmula, un ejemplo resuelto y código de validación.
Valida tu integración
Una vez que tus endpoints de depósitos y retiros estén construidos, usa el Simulador de Webhooks para confirmar que se comportan correctamente — firmas válidas, rechazo de manipulaciones, validación de campos e idempotencia 201/409 — contra solicitudes firmadas reales. Pasar todos los escenarios ahí es el paso de validación antes de que lances el sistema.