# Idempotencia

Reintentar sin pagar dos veces.

Repite una petición con la misma `Idempotency-Key` y obtienes la misma respuesta, sin una segunda operación. Es obligatoria en cada `POST` o `DELETE` con efecto: pagos, cancelaciones, alta y baja de beneficiarios, cobros y direcciones de depósito.

## Reglas

| Situación | Respuesta |
|---|---|
| Misma clave y mismo cuerpo | La misma respuesta, con `Idempotent-Replayed: true` |
| Misma clave y otro cuerpo | `422 idempotency_key_reused`, en producción también pasados 7 días |
| La primera sigue en curso | `409 idempotency_in_progress` con `Retry-After` |
| La primera respondió `202` en un alta | El resultado en cuanto exista, no el mismo `202`. Aplica a cobros, direcciones de depósito y beneficiarios |
| Falta la clave | `400 idempotency_key_required` |
| Clave fuera de largo o duplicada | `400 idempotency_key_invalid` |

La clave mide entre 8 y 200 caracteres y su respuesta se conserva 7 días. Después, la misma clave con el mismo cuerpo se comprueba otra vez sin crear una segunda operación. Va firmada: cambiarla invalida la firma. El alcance es por empresa, ambiente y operación.

## Deriva la clave de tu identificador interno

No uses valores al azar. Si tu proceso se reinicia, debe calcular la misma clave. `pago-factura-1042` sirve; un UUID nuevo en cada intento no. `external_ref` no deduplica por sí solo.

## Ante un timeout

Repite con **la misma clave**. Nunca la cambies: una clave nueva es una orden nueva.

## Qué hacer con la clave tras un rechazo

Todo depende de si el rechazo quedó registrado. `retryable` te lo dice: un rechazo registrado siempre llega con `false`, aunque su código admita reintentos en otros casos.

| Rechazo | Qué pasó | Clave |
|---|---|---|
| `retryable: true`: `422 in_review`, `409 payment_in_flight`, `422 rail_unavailable`, `422 beneficiary_not_approved`, `500`, `503` | No se creó ninguna operación, o no se sabe | La **misma**, cuando la causa pase |
| `retryable: false`: `422 insufficient_funds`, `422 limit_exceeded`, `422 amount_below_minimum`, `422 payment_rejected`, `422 in_review` | El rechazo quedó registrado con esa clave | Una **nueva**: la misma repite el rechazo |
| `409 quote_changed`, `409 quote_expired` | No se creó ningún pago | Una **nueva**, con otra cotización: el cuerpo cambió |
| `400 invalid_request` con `payer.<campo>` en [`POST /v1/collections`](/docs/operaciones/crear-cobro/) | No se creó nada, pero la clave conserva ese cuerpo | Una **nueva** para el cuerpo corregido |
| `422 idempotency_key_reused` | La clave ya identifica otra operación | Corrige tu integración |

## El error `in_review` y el estado `in_review` son distintos

- El **error** `422 in_review` con `retryable: true` responde a tu petición antes de ejecutarla y no hay `pay_`. Repite con la misma clave tras la revisión.
- El mismo error con `retryable: false` quedó registrado al ejecutar: el pago queda `failed`, con `failure_code: in_review`. Esa clave repite el rechazo, así que usa una nueva tras la revisión.
- El **estado** `in_review` es de un pago que ya existe. Ese pago sigue en curso: no lo vuelvas a crear, consúltalo.

## Qué significa cada resultado

- `202 processing` no es fallo. El `pay_` se conserva y se consulta.
- `failed`: el pago no se hizo y no queda dinero en tránsito. Para intentarlo de nuevo, usa una clave nueva.
- `returned`: el saldo ya volvió.

## Errores comunes

- Generar una clave nueva tras un `401` o `403`. Esos errores no dicen si el pago ocurrió: consulta el `pay_`.
- Reutilizar una clave para otro pago: responde `422 idempotency_key_reused`.

## Qué sigue

[Errores](/docs/errores/) y [Reintentos](/docs/recetas/reintentos/).
