Skip to main content
Los endpoints que crean o modifican recursos sensibles exigen el header Idempotency-Key con un identificador único generado por tu sistema. Si la red falla y reintentas con la misma clave y el mismo cuerpo, ZoPay no ejecutará la operación dos veces: devolverá el resultado de la primera ejecución exitosa.

Endpoints que requieren Idempotency-Key

MétodoRuta
POST/payment-intents
POST/addresses
POST/payouts/quote
POST/payouts
POST/events/{event_id}/replay

Recomendación

Usa un UUID v4 por intento de operación nuevo. No reutilices el mismo UUID entre operaciones distintas.
Idempotency-Key: 123e4567-e89b-12d3-a456-426614174000

Ejemplo (crear payment intent)

curl -X POST "https://api.zopay.cash/connect/v1/payment-intents" \
  -H "Authorization: Bearer sk_live_xxxxxxxx" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "25.00",
    "currencies": ["USDT"],
    "external_ref": "user_alice"
  }'
Reintentar la misma petición con la misma clave y mismo cuerpo devuelve el mismo id de intent sin crear uno nuevo.

Reglas

  • Mismo cuerpo: misma clave con cuerpo distinto → 409 idempotency_conflict.
  • TTL: las claves expiran tras un periodo limitado (típicamente 24 h). Pasado ese tiempo no puedes reintentar con la misma clave.
  • Replays silenciosos en sandbox: POST /test/payment-intents/{id}/simulate-deposit no requiere Idempotency-Key — su anchor es el completed_at del intent. Llamarlo dos veces es un no-op.

Errores de validación — 422 vs 400

El OpenAPI documenta dos códigos distintos para errores tipo “bad request”, con significados diferentes. Wirea el manejo de error de tu cliente alrededor de la distinción:
  • 422 Unprocessable Entity — la petición fue estructuralmente inválida. Falló la validación del body schema (Pydantic): un campo requerido faltaba, un campo tenía el tipo equivocado, se mandó un campo desconocido. El campo error.param nombra la llave ofensora. Arregla la forma del body y reintenta.
  • 400 Bad Request — la petición fue bien-formada pero falló un check de negocio / runtime. El body parseó limpio pero algo más lo rechazó: un valor fuera de rango, un conflicto de transición de estado, un asset que no existe en tu catálogo habilitado. Lee error.code y error.message para recuperarte.
Coincide con la convención moderna de Stripe: 400 para “HTTP malo”, 422 para “HTTP bueno, payload semánticamente inválido”. Tu cliente generado debería ramificar primero por el status code; los dos shapes cargan el mismo envelope error. Guía paso a paso en Primer payout y Checkout web (SDK).