Ir al contenido

Webhook — garantías de orden y entrega

Confirmado contra el código del backend (merchants/consumers.py):

  • Síncrono dentro del request que cambia el estado. El signal payment_merchant se dispara dentro del Payment.update(), y el handler corre en el mismo hilo que hace la llamada.
  • Un único intentosession.send(request) sin timeout= ni retry loop.
  • Si el merchant devuelve HTTP > 204 o cae, el webhook se pierde (queda logueado como error del lado backend; no se reintenta).
async function handleWebhook(body) {
const existing = await db.orders.findByB4bitId(body.identifier);
if (existing && new Date(body.edited_at) <= new Date(existing.last_update)) {
// webhook viejo, ya tenemos info más reciente.
logger.debug('Webhook obsoleto, ignorando', { identifier: body.identifier });
return 200;
}
await db.orders.upsertStatus({
b4bit_id: body.identifier,
status: body.status,
last_update: body.edited_at,
raw: body,
});
return 200;
}
  1. Reconciliación diaria: job que corre cada madrugada y llama a GET /orders/?start=AYER&end=HOY&page=1&items_per_page=100, itera todas las páginas y cruza con su base. Para cada pago cuyo edited_at haya avanzado, actualice.
  2. Consulta puntual: si detecta una orden “huérfana” (existe en su checkout pero nunca recibió webhook), consulte GET /orders/info/{identifier} para el estado autoritativo.
  3. Alta disponibilidad del endpoint: reverse proxy con mínimo 2 workers, healthcheck, alertas de downtime.
  4. Respuesta async: HTTP 200 inmediato + cola interna (Redis, SQS, Celery) para procesar la lógica pesada sin bloquear el ACK.

Su handler debe tolerar el mismo (identifier, status) llegando más de una vez:

  • Use el identifier + edited_at como clave única de dedupe.
  • No ejecute side effects (enviar emails, actualizar inventario) más de una vez por estado.
  • Persista el hecho de que “ya procesé este estado” antes de ejecutar el side effect.