Por Lucas Yoris · Performate
Webhooks bajo fuego: reintentos, idempotencia y verificación de firma a escala
Pruebas de carga de webhooks: reintentos, verificación HMAC, replay en ráfaga—buenas prácticas Stripe y patrones k6 de stress.
Los webhooks fallan bajo carga cuando reintentos duplican trabajo, firmas queman CPU y ráfagas superan la concurrencia de handlers—no cuando entregas sueltas funcionan en Postman. La guía de webhooks de Stripe insiste en claves de idempotencia y handlers tolerantes—tus pruebas deben golpear esa realidad antes de que el proveedor de pagos reintente mil eventos payment.succeeded en tu cluster de staging.
Esta guía modela entrega en ráfaga, replay de event_id duplicado, costo de verificación de firma y enlaza efectos de backlog async (pipelines de cola). Obtienes un script k6 de ráfaga con firma HMAC, enrutamiento de decisión por tipo de riesgo y criterios de aborto cuando suben 5xx o tasas de DLQ.
Escenarios a modelar
El perf de webhooks no es latencia media de POST—es forma y duplicación:
- Entrega en ráfaga—tormenta de reintentos del proveedor tras que tu endpoint se ralentizó dos minutos.
- Payloads duplicados con mismo
event_id—mismo evento entregado dos veces; el handler debe 200 o 409, no cobrar doble. - Ruta de firma inválida—muestra baja tasa de 401/403 para asegurar que fallo crypto es barato vs ruta de éxito.
- Backlog en cola downstream—handler acepta rápido pero workers van retrasados; HTTP verde, SLO de negocio rojo.
- Verificación concurrente—HMAC por request a alto RPS CPU-bound en instancias pequeñas.
Forma Stripe vs inbound genérico
Adapta headers (Stripe-Signature, X-Signature custom) a tu proveedor; el patrón k6 se mantiene: firmar body, POST, comprobar códigos idempotentes. Nunca uses secretos de firma de prod en git—solo secretos k6.
k6 ráfaga + eventos duplicados
Ilustrativo—no listo para producción. Incluye escenario de eventos duplicados en plan de prueba extendido; el snippet se centra en ráfaga firmada.
Qué demuestra este ejemplo:
- Spike
ramping-arrival-ratesimula tormenta de reintentos—paso 10 → 200 req/s. - Firma HMAC en script—modela costo CPU de verificación bajo carga.
- Check acepta 200 o 409—409 como éxito idempotente para
event_idduplicado. - Tags
route:webhookpara filtrar de escenarios REST en la misma suite.
import http from 'k6/http';
import { check, sleep } from 'k6';
import crypto from 'k6/crypto';
const secret = __ENV.WEBHOOK_SECRET || 'test_secret';
const BASE = __ENV.API_BASE || 'https://staging.example.com';
function sign(body) {
return crypto.hmac('sha256', secret, body, 'hex');
}
export const options = {
scenarios: {
webhook_burst: {
executor: 'ramping-arrival-rate',
startRate: 10,
timeUnit: '1s',
preAllocatedVUs: 30,
maxVUs: 150,
stages: [
{ duration: '1m', target: 10 },
{ duration: '30s', target: 200 },
{ duration: '2m', target: 200 },
{ duration: '1m', target: 10 },
],
tags: { suite: 'webhook_burst' },
},
},
thresholds: {
http_req_failed: ['rate<0.02'],
'http_req_duration{route:webhook}': ['p(95)<1500'],
},
};
export default function () {
const body = JSON.stringify({
event_id: `evt-${__VU}-${__ITER}`,
type: 'payment.succeeded',
});
const res = http.post(`${BASE}/webhooks/inbound`, body, {
headers: {
'Content-Type': 'application/json',
'X-Signature': sign(body),
},
tags: { route: 'webhook', suite: 'webhook_burst' },
});
check(res, { accepted: (r) => r.status === 200 || r.status === 409 });
sleep(0.05);
}
Patrones que funcionan
- Subconjunto duplicado—10% iteraciones reutilizan lista fija de
event_id—valida dimensionado del store de idempotencia. - Tripwire de aborto en 5xx > 1% durante ráfaga—para antes de corromper datos de staging compartido.
- Monitorear DLQ e idempotency DB durante corrida—HTTP es necesario, no suficiente.
- Solo eventos sintéticos—sin ids de clientes prod en payloads (GDPR).
Antipatrones a evitar
- Prueba de carga contra URL webhook de prod sin aprobación del proveedor.
- Medir solo tasa 200—ignorar semántica 409 y efectos secundarios duplicados.
- Ignorar backlog de workers async—emparejar con lag de cola.
- Cero fallos de firma en plan de prueba—regresiones crypto se esconden hasta intentos de falsificación en prod.
Pro tip (comando de ejemplo): exportar stats solo webhook para equipo de pagos.
k6 run webhook-burst.js --summary-trend-stats="p(95),p(99)" --tag route=webhook
Qué demuestra este comando: summary filtrado se adjunta a revisión de pagos sin ruido REST de escenarios no relacionados en el mismo repo.
Marco de decisión: riesgo vs prueba
| Riesgo | Prueba | Criterio de pass |
|---|---|---|
| Eventos duplicados | Repetir mismo event_id en subconjunto | 200 o 409; sin efectos secundarios dobles |
| Tormenta de retry | Spike ARR | Tasa de error + DLQ acotada |
| Falsificación | Firmas inválidas baja tasa | 401 rápido; sin enqueue worker |
| CPU handler | Ráfaga con firma activa | p95 dentro SLO bajo spike |
| Lag end-to-end | HTTP + tendencia lag cola | SLA de negocio en tiempo de completado |
Observabilidad y checklist pre-corrida
- Store de idempotencia dimensionado para duplicados en ráfaga—documentar cardinalidad máxima de
event_idprobada. - Monitoreo DLQ durante prueba con owner de aborto nombrado.
- Solo eventos sintéticos; secreto de firma desde perfil env.
- Workers downstream escalados conscientemente—interpretar HTTP vs lag por separado.
- Comparar ráfaga con lenguaje de spike test en runbook de lanzamiento.
- Archivar export para pagos/cumplimiento si flujos regulados tocados.
Cómo Performate apoya pruebas de carga de webhooks
Abajo hay un ejemplo de flujo concreto para webhooks inbound de pagos—adapta header de firma a tu proveedor.
Ejemplo: import Postman → escenario ráfaga → export pagos
- Importa request webhook desde Postman—plantilla de body preservada. Problema resuelto: payload de firma coincide con lo que pruebas funcionales ya usan.
- Añade lógica pre-request de firma en k6 exportado—HMAC desde secreto env. Problema resuelto: ruta crypto incluida sin reimplementación manual cada sprint.
- Configura stages de ráfaga en UI—spike 30s a 200 req/s. Problema resuelto: stages visuales reducen errores YAML antes de revisión de pagos.
- Corre con tripwire de aborto en 5xx—owner Slack etiquetado. Problema resuelto: staging compartido protegido durante ráfaga agresiva.
- Exporta reporte para equipo de pagos—tasa 409 visible junto a 200. Problema resuelto: historia de idempotencia contada con números, no walkthrough de código.
- Promueve smoke de entrega única a CI—ráfaga queda manual pre-release. Problema resuelto: regresión diaria en cableado sin tormenta de retry diaria.
Ese flujo mapea directamente al cta de este post: escenarios webhook ejecutables y reportes compartibles sin sprints de glue code.
Cierre
El perf de webhooks es ráfagas + duplicados + crypto, no latencia media de POST. Programa un spike con forma de reintento antes del próximo incidente de pagos; valida rutas 409; vigila DLQ mientras HTTP sigue viéndose bien.
Importa tu webhook inbound, añade firma, corre una ráfaga en staging y registra dónde empiezan los 5xx—esa tasa pertenece al runbook de pagos.
¿Listo para optimizar el rendimiento de tu API?
Usa Performate para convertir este playbook en escenarios k6 ejecutables, umbrales y reportes compartibles sin perder días en código de integración.