Idempotencia
Reintenta cualquier solicitud sin miedo a crear leads duplicados.
El problema
Tu servidor manda un lead, la red se corta antes de recibir la respuesta y tu código reintenta. Sin protección, ese reintento crea un segundo lead. Con la cabecera Idempotency-Key, el reintento devuelve la respuesta de la primera vez.
Cómo se usa
Manda una cabecera Idempotency-Key con un valor único por envío: entre 1 y 200 caracteres ASCII imprimibles. Buenos candidatos: el id del registro en tu base de datos, el id del envío del formulario, o un UUID que generes antes del primer intento y reutilices en cada reintento.
curl -X POST https://api.zevra.co/v1/leads \
-H "Authorization: Bearer zvr_live_TU_LLAVE" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: formulario-web-8f3c2a" \
-d '{ "nombre": "Ana Ruiz", "telefono": "+52 55 0000 0001" }'La llave se recuerda 24 horas, por origen (por llave de API).
Qué pasa en cada caso
| Situación | Respuesta |
|---|---|
| Primera vez que vemos la llave | La solicitud se procesa normal: 201 created o 200 enriched. |
| Misma llave, mismo cuerpo, dentro de 24 horas | La respuesta original, con el mismo código HTTP y el mismo lead_id, más "replay": true. No se toca el CRM. |
| Misma llave, cuerpo distinto | 409 idempotency_conflict. Una llave es de un envío; si el contenido cambió, usa otra. |
| Misma llave mientras la primera solicitud sigue en curso | 409 idempotency_in_progress con Retry-After: 1. Espera un segundo y reintenta. |
| Misma llave después de 24 horas | Cuenta como la primera vez. |
Una respuesta repetida se ve así:
{
"lead_id": "7b1d9c1e-2f4a-4d0b-9a3c-5e6f7a8b9c0d",
"status": "created",
"lead_url": "https://dashboard.zevra.co/dashboard/leads?leadId=7b1d9c1e-2f4a-4d0b-9a3c-5e6f7a8b9c0d",
"replay": true,
"request_id": "req_9c2e7a1b4d6f8e0a2c4b6d8f"
}El request_id es el del reintento (cada intento tiene el suyo), pero lead_id, status y lead_url son los originales.
Solo los éxitos reservan la llave
Esta es la parte que importa cuando algo sale mal. Si una solicitud con Idempotency-Key falla (un 400 por un campo inválido, un 422 porque la propiedad no existe, un 500 nuestro), la llave queda libre. Corrige el cuerpo y vuelve a mandarlo con la misma llave: se procesa como si fuera la primera vez.
Dicho de otra forma: la llave solo se "gasta" cuando te devolvimos un 201 o un 200. Un intento fallido nunca bloquea al reintento corregido.
Comparar cuerpos
Para decidir si dos solicitudes son "la misma", la API compara el cuerpo JSON de forma canónica: el orden de las llaves y los espacios en blanco no importan, los valores sí. {"nombre":"Ana","telefono":"55..."} y { "telefono": "55...", "nombre": "Ana" } son el mismo cuerpo.
Modo de prueba
Una solicitud con X-Zevra-Dry-Run: true nunca reserva ni consulta llaves de idempotencia, aunque la mandes. El modo de prueba no escribe nada, así que no hay nada que proteger.
Recomendaciones
- Genera la llave antes del primer intento y guárdala junto al registro; así cualquier reintento, incluso desde otro proceso, manda la misma.
- Un formulario que se envía dos veces por doble clic es el caso típico: usa el id del envío como llave y el segundo clic devuelve la respuesta del primero.
- Si usas Zapier o Make, la mayoría de los disparadores ya traen un id único del evento. Ese id es tu llave.