webhooks:read (GET), webhooks:write (POST/PATCH/DELETE/test/replay).
Cuándo la entrega es exitosa
Un 2xx o cualquier 3xx (301, 302, 303, 307, 308 y el resto) cuenta como entrega exitosa. El status que ves en el log es el real: un Google Apps Script que responde 302 después dedoPost queda como 302 exitoso, no como fallo. No reintentamos y no repetimos el POST contra Location (eso volvería a ejecutar el handler).
Si hay Location, Waspy puede hacer un GET —recién después de revalidar esa URL— solo para anotar la respuesta del redirect en el registro. Ese GET no define el resultado: si falla o el destino no es seguro, la entrega sigue exitosa.
Un 4xx, un 5xx o un timeout de 10s es un fallo, entra en los reintentos y suma a las 10 fallas que auto-desactivan la suscripción.
La misma regla aplica a las entregas reales, a POST /webhooks/:id/test y al reenvío manual POST /webhooks/:id/replay/:deliveryId.
GET /webhooks
Lista las suscripciones del tenant.POST /webhooks
Crea una suscripción nueva. Elsecret se devuelve una sola vez.
Headers custom
headers es un objeto de strings. Waspy los manda en cada entrega: el primer intento, los reintentos y POST /webhooks/:id/test. La firma HMAC no cambia: se calcula igual sobre <t>.<rawBody>.
Límites:
- Hasta 10 headers.
- El nombre es un token RFC 7230 (letras, dígitos y símbolos como
!,#,-,.,_), de hasta 256 caracteres. No distingue mayúsculas:X-Tokenyx-tokencuentan como el mismo. - El valor es texto de hasta 1024 caracteres, sin saltos de línea.
- No se puede pisar
Content-Type,Content-Length,Host,X-Waspy-Signature,X-Waspy-Webhook-Id,X-Waspy-Webhook-Event,X-Waspy-Webhook-Version,X-Waspy-Webhook-AttemptniX-Waspy-First-Attempt-At.
GET /webhooks y GET /webhooks/:id devuelven solo los nombres, con el valor enmascarado:
headers es null.
El mismo campo existe en el panel, POST /api/v1/webhook-subscriptions y PATCH /api/v1/webhook-subscriptions/:id.
GET /webhooks/:id
Devuelve una suscripción individual (sin secret).PATCH /webhooks/:id
Actualizaurl, events, description, enabled o headers. Si pasás enabled: true, se limpia el estado de auto-disable y se resetea el contador de fallas.
headers reemplaza el conjunto entero. null o {} los borra. Si omitís el campo, quedan como estaban.
DELETE /webhooks/:id
Borra la suscripción.POST /webhooks/:id/test
Encola un evento sintéticomessage.received con datos dummy. Útil para validar firma + URL en desarrollo. El criterio de éxito es el mismo que en producción: 2xx o cualquier 3xx cuenta como entregado, y no se sigue el redirect con otro POST.
Test deliveries SÍ aparecen en
GET /webhooks/:id/deliveries (con statusCode, errorMessage, etc), pero NO actualizan lastDeliveryAt, lastDeliveryStatus ni consecutiveFailures en la subscription — para no mezclar bookkeeping de pruebas con producción. Si querés confirmar que el test golpeó tu endpoint, consultá GET /webhooks/:id/deliveries?status=all&limit=5 justo después.samplePayload con el envelope exacto que se va a entregar a tu URL — útil para configurar la verificación de firma localmente antes de triggerear:
GET /webhooks/:id/deliveries
Lista los intentos de entrega individuales de una suscripción. Cada delivery (incluso fallido) genera una fila acá. Cursor-based, newest first. Scope:webhooks:read
id que devuelve son los deliveryId que podés pasarle a POST /webhooks/:id/replay/:deliveryId.
POST /webhooks/:id/rotate-secret
Genera un nuevo secret HMAC e invalida el anterior inmediatamente. Las entregas en cola que se hayan firmado con el secret viejo van a fallar la verificación en tu endpoint. Scope:webhooks:write
WASPY_WEBHOOK_SECRET antes de la próxima delivery.
POST /webhooks/:id/replay/:deliveryId
Re-encola un delivery histórico (de la tablawebhook_deliveries). Útil para reenviar a mano entregas que fallaron después de exhaustar reintentos automáticos. El reenvío usa el mismo criterio de éxito que una entrega normal: 2xx y cualquier 3xx cuentan como entregados, y no se sigue el redirect con otro POST.
El payload exacto del delivery original no se almacena verbatim (sólo su hash). El replay re-fires un evento con el mismo
event y datos sintéticos — tu integración debe ser idempotente sobre data.id para que esto sea seguro.