Idempotencia y reintento
Hay dos niveles de identidad, y no se mezclan:
- Operación lógica:
idempotency_key+external_event_id, en el cuerpo. Se mantienen iguales en cada reintento y nunca se reutilizan para otra operación, ni siquiera después de que los datos caduquen. - Intento de envío:
X-FTID-Request-Id,X-FTID-Timestampy la firma. Cambian en cada intento.
Qué hace el servidor con la operación
Sección titulada «Qué hace el servidor con la operación»| Situación | Respuesta |
|---|---|
| Clave nueva | 201, con la evaluación recién almacenada |
| Misma clave, mismo contenido, resultado aún dentro de su ventana de historial aprobada | 200, con exactamente la evaluación ya almacenada, sin volver a calcularla, incluso si el occurred_at original ya está fuera de la ventana de ±300 s |
| Misma clave, mismo contenido, resultado fuera de su ventana de historial aprobada | 410 assessment-result-expired, sin resultado histórico ni identificador de evaluación; sin recálculo ni uso nuevo |
Mismo idempotency_key o mismo external_event_id con contenido distinto |
409 idempotency-conflict, sin efecto parcial |
Referencias de persona, cuenta o sesión incompatibles con vínculos ya almacenados, por ejemplo un account_ref enviado por primera vez con otro subject_ref |
409 binding-conflict, sin almacenar nada; no reintentable |
“Contenido” significa los diez campos del cuerpo distintos de deadline_ms. Cambiar solo
deadline_ms mantiene la misma operación. Cambiar cualquier otro campo genera un conflicto,
incluido reemplazar null por un token, cambiar el token o reescribir occurred_at con otra grafía
(Z y +00:00 son grafías distintas).
El event_context_token es de un solo uso y se consume junto con la evaluación. Un reintento
de la misma operación no lo vuelve a consumir; una operación nueva con un token ya usado recibe
400 event-context-invalid.
{ "type": "https://fingertrackid.dev/problems/idempotency-conflict", "title": "Chave de idempotência já usada com outro conteúdo", "status": 409, "correlation_id": "corr-problem-idempotency-0001", "retryable": false}Cuándo reintentar
Sección titulada «Cuándo reintentar»| Resultado | ¿Reintentar? | Cómo |
|---|---|---|
201 o 200 |
No | Use la evaluación |
401 timestamp-out-of-window |
Sí | Corrija el reloj; nuevo intento |
401 request-replayed |
Sí | Nuevo intento, y corrija el código que reutilizó el request_id |
429 rate-limited |
Sí | Espere lo indicado en Retry-After (hoy 1 s; al menos 1 s si falta); nuevo intento |
503 assessment-unavailable |
Sí | Espere lo indicado en Retry-After (hoy 1 s; al menos 1 s si falta); nuevo intento. El estado puede ser indeterminado |
410 assessment-result-expired |
No | El resultado antiguo ya no está disponible. Detenga los reintentos de conciliación; conserve los IDs de operación originales y no regenere la operación |
| Error de red, tiempo de espera agotado, respuesta truncada o respuesta de más de 65536 bytes | Sí | Trátelo como indeterminado; nuevo intento con el mismo cuerpo |
Cualquier otro 4xx, o 500 |
No | Corrija la causa (vea errores) |
Un nuevo intento es siempre: el mismo cuerpo, un request_id nuevo, la marca de tiempo
actual y una firma nueva. Esto también vale para 429 y 503: el request_id del intento
respondido puede ya estar consumido. Reintente un número acotado de veces, nunca en bucle.
Attempt 1 same body bytes X-FTID-Request-Id: <new UUIDv7> X-FTID-Timestamp: <now> X-FTID-Signature: <new> → 503 assessment-unavailable, Retry-After: 1
wait at least Retry-After
Attempt 2 the same body bytes X-FTID-Request-Id: <another new UUIDv7> X-FTID-Timestamp: <now> X-FTID-Signature: <new> → 200 with the assessment already stored, or 201 if the first attempt stored nothingTiempo de espera agotado sin respuesta
Sección titulada «Tiempo de espera agotado sin respuesta»Tras un tiempo de espera agotado, la evaluación puede haberse almacenado sin que la respuesta le
llegara. Un reintento con el mismo cuerpo lo resuelve: un resultado retenido devuelve 200; un
resultado caducado devuelve un 410 no reintentable. Use una política de reintentos acotada:
backoff, Retry-After y el plazo de su operación de negocio. Una vez agotado ese plazo, aplique su
propio comportamiento de contingencia y concilie más tarde, con el mismo cuerpo.
Un cuerpo que lleva un event_context_token solo se concilia de esta forma si el primer intento se
almacenó, o mientras aún no haya llegado el expiresAt del token (como máximo la
ventana de evidencia de su aplicación, 60 o 300
segundos, después de su captura). De lo contrario, el mismo cuerpo devuelve
400 event-context-invalid; en ese caso, evalúe el evento como una operación nueva con null
(guía de integración).
No existe un GET de recuperación: la recuperación es el mismo POST.