Ucotron Cortex
Guides by flow

Forecast, watch and webhooks

Subscribe, receive the alert, verify the HMAC signature, handle retries and the DLQ.

Diagrama: Forecast, watch y webhooksFlujo del ciclo de webhooks: la suscripción de watch produce entregas con firma, con reintentos, DLQ y replay manual.

2xx

5xx

falla

POST /v1/watch/subscriptions

Alerta

Evento de webhook

Intento 1

delivered

retry_scheduled

Intento 3

dlq

POST .../replay

Diagrama: Forecast, watch y webhooksFlujo del ciclo de webhooks: la suscripción de watch produce entregas con firma, con reintentos, DLQ y replay manual.

2xx

5xx

falla

POST /v1/watch/subscriptions

Alerta

Evento de webhook

Intento 1

delivered

retry_scheduled

Intento 3

dlq

POST .../replay

Fuente del diagrama (mermaid)
flowchart LR
    W[POST /v1/watch/subscriptions] --> A[Alerta]
    A --> E[Evento de webhook]
    E --> D1[Intento 1]
    D1 -- 2xx --> OK[delivered]
    D1 -- 5xx --> D2[retry_scheduled]
    D2 --> D3[Intento 3]
    D3 -- falla --> DLQ[dlq]
    DLQ --> RP[POST .../replay]
    RP --> OK

Verifying the signature

Every delivery carries these headers:

HeaderWhat it is
x-ucotron-event-idThe event id
x-ucotron-signature-timestampWhen it was signed
x-ucotron-payload-digestSHA-256 of the canonical payload
x-ucotron-signatureHMAC-SHA256 of eventId.timestamp.digest

Verify in this order: id, time window (300 s), duplicate, digest, signature. Comparing the signature with === instead of constant time turns your receiver into an oracle.

Retries and the DLQ

Three attempts at most, with deterministic backoff (1 s, 5 s, 15 s). Only 408, 429, 500, 502, 503 and 504 are retried: a 400 is not fixed by retrying.

Ciclo de vida de una entrega de webhook Un evento de la suscripción de watch produce una entrega firmada. Si el endpoint responde 2xx queda entregada. Si falla con un status reintentable, se reintenta con backoff determinista (1, 5 y 15 segundos, tres intentos como máximo); agotados los reintentos cae a la cola de mensajes muertos (DLQ), desde donde un replay manual vuelve a generar una entrega. Cada intento queda auditado con su firma y su respuesta. EVENTO del watch forecast.watch.triggered DELIVERY POST firmado (HMAC) a tu endpoint 2xx ENTREGADO 408 · 429 · 5xx reintentable RETRY 1 s · 5 s · 15 s — máximo tres intentos reintenta reintentos agotados DLQ cola de muertos, visible por API REPLAY manual POST …/deliveries/:id/replay nueva entrega Cada intento queda auditado: número, timestamp, firma enviada y respuesta recibida. El replay no reescribe la historia — genera una entrega nueva.
bash
curl -sS "$SBOX/v1/webhooks/deliveries?limit=250" -H "authorization: Bearer $TOKEN"
curl -sS "$SBOX/v1/webhooks/deliveries/<deliveryId>/replay" -X POST \
  -H "authorization: Bearer $TOKEN" -H "idempotency-key: replay-demo-1"

The sandbox seeds one delivery in each final statedelivered, retry_scheduled and dlq — on purpose. If you could only exercise the 200, you would write your retry handling by looking at this page.

What breaks

  • Only what landed in the DLQ can be retried. Retrying something already delivered would duplicate the event on the receiver's side: 409 delivery_not_replayable.
  • An endpoint pointing at a private IP or the cloud metadata service is rejected with ssrf_blocked before any attempt.
  • The same event delivered twice is detected as replay_detected. Your receiver must be idempotent anyway: the network does not promise exactly-once delivery.

Endpoints in this guide

On this page