Saltar al contenido principal

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ónMétodoRuta
DepósitosPOST{affiliate_base_url}/notifications/apuesteria/deposits/
RetirosPOST{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.

CampoTipoDescripción
statusstringResultado de la transacción de nuestro lado (p. ej. success).
codestringCódigo de resultado de la transacción (p. ej. 0000).
point_of_sale.idintegerIdentificador del punto de venta al que pertenece la transacción.
point_of_sale.namestringNombre legible del punto de venta.
point_of_sale.currency_codestringMoneda del punto de venta (ISO 4217, p. ej. MXN).
deposit / withdrawal.usernamestringNombre de usuario del usuario final de la transacción.
deposit / withdrawal.descriptionstringDescripción legible de la transacción.
deposit / withdrawal.transaction_numberstringIdentificador único de la transacción. Úsalo como tu clave de idempotencia.
deposit / withdrawal.amountnumberMonto de la transacción.
deposit / withdrawal.currency_codestringMoneda de la transacción (ISO 4217, p. ej. MXN).
created_atstringMarca 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_number dado → regístrala y devuelve 201 Created.
  • Cualquier notificación posterior que traiga un transaction_number que ya registrasteno la apliques de nuevo y devuelve 409 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_number sigue una sola regla: devuelve el mismo valor exacto que te enviamos siempre que hayas podido leerlo; en caso contrario, envía null. Solo hay dos casos en los que no pudiste leerlo — rechazaste la solicitud antes de parsear el cuerpo (una firma incorrecta), o el campo transaction_number no 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.
  • message debe 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.

StatusCuándoCuerpo
400 Bad RequestPayload inválido o vacío{"status":"error","transaction_number":null,"message":"JSON data is empty"}
401 Unauthorized / 403 ForbiddenFalló la validación de firma{"status":"error","transaction_number":null,"message":"Invalid signature"}
404 Not FoundPunto de venta no encontrado{"status":"error","transaction_number":"4345FF2XB7F323CD","message":"Point of sale ID is not found"}
409 Conflicttransaction_number duplicado{"status":"error","transaction_number":"4345FF2XB7F323CD","message":"Transaction already processed"}
500 Internal Server ErrorError 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.