# Zypher Global — API > API de pagos Zypher Global, PIX (BRL), SPEI (MXN) + USDT en una sola integración. Cash-in, cash-out, consulta de estado, splits y webhooks, autenticados por Client ID/Secret. Base URL: https://zypher.lat Docs: https://zypher.lat/es/docs ## Acerca de Una sola integración sirve tanto para quien recibe/paga con su propia cuenta como para quien opera como plataforma/adquirente: cash-in (dinámico y clave/CLABE fija), cash-out, splits, saldo, conversión de moneda, consulta de transacciones y webhooks. # Introducción Integra con tu `Client ID` y `Client Secret` para crear cobros (cash in), solicitar retiros (cash out) y consultar el status. Opcionalmente, indica un `webhook_url` para recibir notificaciones automáticas. Tip: genera tu clave en Dashboard → API. Las claves se crean con los accesos iniciales necesarios. **URL base** ``` https://api.zypher.lat ``` ### Primeros pasos - Genera tus credenciales en Dashboard → API (Client ID y Client Secret). - Crea un cobro (cash in) en la moneda deseada, ve Cobro PIX (BRL). - Configura tu `webhook_url` para recibir las confirmaciones automáticamente. - Para pagar, usa el cash out por clave PIX, BR Code o CLABE, ve Retiro PIX (clave). # Autenticación Envía credenciales mediante encabezados o Basic Auth. **Headers** ``` X-Client-Id: Client ID X-Client-Secret: Client Secret ``` **Basic Auth** ``` Authorization: Basic base64(ClientID:ClientSecret) ``` La autenticación y la creación de cobros (cash in) NO requieren IP configurado. La restricción por IP (IPv4 en la allowlist de la clave) aplica solo al cash out vía API. # Sandbox El Sandbox te permite validar toda tu integración (crear cobro, recibir el webhook, consultar estado) SIN mover dinero real. Nada se confirma de verdad, es 100% aislado. Una solicitud con la Clave de Prueba NUNCA toca adquirente, saldo ni transacción real. Las respuestas tienen el mismo formato de producción (+ el campo `"sandbox": true`). ### Clave de Prueba (pública) Usa estas credenciales ya disponibles, no necesitas generar nada. Son públicas a propósito (el sandbox no mueve dinero). **Headers** ``` X-Client-Id: xpagsandbox_00000000 X-Client-Secret: 202620262026202620262026 ``` ### 1) Crear un cobro de prueba POST `/cashin` · misma ruta de producción. Cambia `currency` por `BRL` · `MXN` para probar cada moneda. **Body** ``` { "currency": "BRL", "amount": 100.00, "external_id": "TESTE-001", "webhook_url": "https://seusistema.com/webhooks/zypherglobal" } ``` **Respuesta** ``` { "sandbox": true, "ok": true, "status": "pending", "currency": "BRL", "amount": 100, "fee": 3, "request_number": "sbx_...", "transaction_id": "SBX-...", "copy_paste": "00020101...TEST...", "expire_minutes": 30 } ``` Guarda el `transaction_id`, es el que usas para simular el pago en el siguiente paso. Cada moneda devuelve en la respuesta la instrucción de pago correspondiente (ej.: `clabe` en MXN, `payee_data` en COP). ### 2) Simular el pago (dispara el webhook) POST `/sandbox/simulate` · confirma (o falla) el cobro de prueba y dispara el webhook a tu `webhook_url`, idéntico al de producción. **Body** ``` { "transaction_id": "SBX-...", "outcome": "paid" } ``` `outcome`: `paid` · `failed` · `expired` — prueba todos los escenarios de tu handler. **Webhook enviado a tu URL** ``` { "sandbox": true, "type": "cashin", "status": "confirmed", "amount": 100, "fee": 3, "transaction_id": "SBX-...", "external_id": "TESTE-001", "e2e": "E...", "provider": "Zypher Global" } ``` ### Saldo y retiro de prueba En el sandbox el `/balance` devuelve un saldo de prueba alto, así puedes probar el `/cashout` (retiro) sin fondos reales. El retiro de prueba también acepta el `/sandbox/simulate` para simular la liquidación. La conversión de moneda aún NO está disponible en el sandbox (mueve saldo real). Todo lo demás, cash in, cash out, estado, saldo y webhook, funciona. # Cobro PIX (BRL) POST `/cashin` · `"currency": "BRL"` Ruta única para todas las monedas, la moneda se define por el campo `currency` (por defecto `BRL`). El campo `amount` decide el MODO: `com amount` = cobro DINÁMICO (valor fijo por cobro); `sem amount` = tu CLAVE PIX FIJA (valor abierto, el pagador elige cuánto). ### Cobro dinámico (por valor) Envía el `amount` → devolvemos un QR PIX de VALOR FIJO para ESE pago. Cada cobro es único y desaparece tras pagarse/expirar. Es el flujo del día a día (checkout, pedido único). **Body** ``` { "currency": "BRL", "amount": 123.45, "name": "Cliente Pix", "document": "12345678900", "description": "Pedido #123", "external_id": "PEDIDO-123", "webhook_url": "https://seusistema.com/webhooks/zypherglobal", "splits": [ { "username": "usuario1", "percentage": 10 }, { "username": "usuario2", "percentage": 5 } ] } ``` **Respuesta** ``` { "ok": true, "qr_img": "data:image/png;base64,...", "copyPaste": "000201...", "code": "...", "request_number": "pr_...", "transaction_id": "pr_..." } ``` Consulta luego por el `request_number` (un solo pago). Ver Consultar Transacciones. ### Clave PIX fija (valor abierto · estática) Llama a la MISMA ruta `/cashin` SIN el campo `amount` → devolvemos un QR de VALOR ABIERTO (el pagador elige cuánto pagar). CADA llamada genera un QR NUEVO (copy-paste propio) vinculado al `external_id` que TÚ envías. Autenticación, KYC y cuenta son los mismos del cash-in normal. **Body** ``` { "currency": "BRL", "external_id": "PEDIDO-123", "webhook_url": "https://seusistema.com/webhooks/zypherglobal", "splits": [ { "username": "usuario1", "percentage": 10 } ] } ``` **Respuesta** ``` { "ok": true, "static": true, "open_value": true, "external_id": "PEDIDO-123", "copyPaste": "00020126580014br.gov.bcb.pix...", "qr_url": "https://quickchart.io/qr?text=...", "transaction_id": "acc000094602601" } ``` - El `external_id` es TUYO (tú lo envías, nosotros NO lo generamos). Queda vinculado al QR y vuelve en el webhook. Como cada llamada genera un QR nuevo, genera UNO por pedido → rastreo por pedido. - ¿Reutilizaste el MISMO QR en varios pagos? Todos vuelven con el MISMO `external_id`, concilia por el `e2e` (único por pago). El crédito siempre es individual (bloqueo anti-duplicados por el `e2e`). - El campo `origin: "static"` distingue este flujo del QR dinámico en el webhook. ¿Revendes a tus propios clientes? El `external_id` es el pedido de TU sistema, el valor entra en tu saldo y tú retransmites el webhook al cliente final. Splits, webhook y consulta funcionan igual en los dos modos: Splits (reparto) · Webhooks · Consultar Transacciones. # Cobro SPEI (MXN) POST `/cashin` · `"currency": "MXN"` Genera una referencia SPEI/CLABE para el pagador en México. El campo `amount` decide el MODO: `com amount` = cobro DINÁMICO (CLABE por valor); `sem amount` = la CLABE FIJA de la cuenta (valor abierto). La confirmación llega a tu webhook. ### Cobro dinámico (por valor) Envía el `amount` → devolvemos una CLABE/referencia para ESE pago. Es el flujo del día a día (pedido único). **Body** ``` { "currency": "MXN", "amount": 500.00, "name": "Juan Pérez", "document": "PEPJ800101HDFRRL09", "description": "Pedido #123", "external_id": "PEDIDO-123", "webhook_url": "https://seusistema.com/webhooks/zypherglobal", "splits": [ { "username": "operador_mx", "percentage": 10 } ] } ``` **Respuesta** ``` { "ok": true, "reference": "REF123456", "clabe": "012345678901234567", "amount": 500.00, "fee": 5.00, "currency": "MXN", "request_number": "pr_...", "transaction_id": "pr_...", "status": "pending", "bank_name": "STP (Sistema de Transferencia y Pagos)", "beneficiary": "Zypher" } ``` Consulta luego por el `request_number`/`transaction_id` (un solo pago). Ver Consultar Transacciones. Toda respuesta MXN (dinámica y CLABE fija) incluye `bank_name` y `beneficiary`. Muestra estos datos en tu pantalla de pago junto a la CLABE: que el pagador vea el banco (`STP`) y el nombre del beneficiario aumenta la confianza y la conversión del SPEI. ### CLABE por venta (valor abierto · estática) Llama a la MISMA ruta `/cashin` con `"currency": "MXN"` SIN el campo `amount`, enviando un `external_id` por venta → devolvemos una CLABE de VALOR ABIERTO solo de esa venta. El mismo `external_id` devuelve la MISMA CLABE (idempotente en el retry); `external_id` nuevo = CLABE nueva. El pagador envía cualquier valor vía SPEI. Autenticación, KYC y SPEI activo son los mismos del cash-in normal. **Body** ``` { "currency": "MXN", "external_id": "PEDIDO-123", "webhook_url": "https://seusistema.com/webhooks/zypherglobal", "splits": [ { "username": "operador_mx", "percentage": 10 } ] } ``` **Respuesta** ``` { "ok": true, "static": true, "open_value": true, "currency": "MXN", "external_id": "PEDIDO-123", "clabe": "012345678901234567", "transaction_id": "spc...", "bank_name": "STP (Sistema de Transferencia y Pagos)", "beneficiary": "Zypher" } ``` - Cada venta gana su PROPIA CLABE (18 dígitos). Dos leads de la MISMA cuenta (ej.: Lucas y Maria) NUNCA colisionan, cada uno tiene su CLABE y su `external_id`. Una venta no cae sobre la otra. - El `external_id` es TUYO (tú lo envías, nosotros NO lo generamos; solo lo devolvemos en el webhook). Es la CLAVE del reuso: mismo `external_id` = misma CLABE (idempotente); `external_id` nuevo = CLABE nueva. - Cada CLABE es de valor ABIERTO y puede recibir varios pagos, cada uno genera su propio webhook con `e2e` único (clave de conciliación por pago). - Cuando paga, la `receiverClabe` en el webhook identifica la venta → disparamos el webhook con el `external_id` correcto. El campo `origin: "static"` distingue este flujo del depósito por valor. SIN `external_id` → devolvemos una CLABE PERMANENTE de la cuenta (dirección de cobro/wallet); ahí no existe atribución por venta, concilia por `e2e`. Para automatizar ventas, envía SIEMPRE un `external_id` por venta. ¿Revendes a tus propios clientes? El `external_id` es el pedido de TU sistema, el valor entra en tu saldo MXN y tú retransmites el webhook al cliente final. Como la CLABE fija no tiene `transaction_id` por pago, consulta la LISTA de cobros por `clabe`/`external_id` en Consultar Transacciones. Splits y webhook: Splits (reparto) · Webhooks. ### OXXO (ficha pagada en efectivo) Misma ruta `/cashin` y el MISMO saldo MXN, pero con `"method": "OXXO"`: generamos una ficha que el pagador lleva a cualquier tienda OXXO y paga en efectivo. La respuesta ya trae la referencia y el código de barras al instante, para mostrarlos en tu pantalla. Rango aceptado: 10 a 10,000 MXN, y el pagador debe pagar el monto exacto. **Body** ``` { "currency": "MXN", "method": "OXXO", "amount": 250.00, "payerData": { "name": "Luis Hernandez", "email": "luis@correo.com" }, "external_id": "PEDIDO-123", "webhook_url": "https://seusistema.com/webhooks/zypherglobal", "splits": [ { "username": "operador_mx", "percentage": 10 } ] } ``` - `method` — `"OXXO"`. Obligatorio (es lo que lo distingue del SPEI/CLABE). - `amount` — entre 10 y 10,000 MXN. Fuera del rango devuelve `amount_below_min`/`amount_above_limit`. - `payerData` (`name`, `email`) — opcionales: se completan automáticamente si se omiten. - `generateCheckout` — opcional (por defecto `true` = página alojada). Usa `false` para recibir la ficha cruda en `payee_data` y mostrarla en tu pantalla. - `splits` — opcional: OXXO acepta reparto (split) igual que SPEI/PIX, aplicado en el crédito cuando el pago se confirma. Ver Splits (reparto). **Respuesta (`generateCheckout: false`)** ``` { "ok": true, "currency": "MXN", "amount": 250.00, "fee": 5.00, "method": "OXXO", "request_number": "oxxo_...", "transaction_id": "oxxo_...", "status": "pending", "awaiting_instruction": false, "transfer_type": "REFERENCE", "payee_data": { "transferType": "REFERENCE", "reference": "8204240000119882", "barcode": "https://static.muwe.mx/.../abc.png" } } ``` - `reference` — número de la referencia OXXO que el pagador indica en la caja. - `barcode` — URL de imagen (PNG) del código de barras, muéstralo directo en tu pantalla. La ficha queda `pending` hasta que el pagador va a la tienda (puede tardar horas o días, normal para efectivo). La confirmación llega a tu webhook cuando se realiza el pago y acredita tu saldo MXN. Reconcilia por `request_number`/`external_id`. **Webhook, cuando el pagador paga en la tienda** ``` { "type": "cashin", "origin": "dynamic", "status": "confirmed", "amount": 250.00, "fee": 5.00, "request_number": "oxxo_...", "transaction_id": "oxxo_...", "external_id": "PEDIDO-123", "e2e": "E...", "provider": "Zypher Global" } ``` - Solo se dispara cuando el pago está confirmado (la ficha puede quedar `pending` por horas/días). Concilia por `transaction_id`/`request_number`/`external_id`, el mismo que guardaste al generar. - Si enviaste `splits`, el reparto ya se aplicó en el crédito antes de este webhook. Detalles en Webhooks. # Cobro USDT POST `/cashin` · `"currency": "USDT"` El pagador envía USDT (red TRON/TRC20) a la `deposit_address`. La respuesta trae el `amount` EXACTO a enviar (la base + un ajuste de centavos que identifica el cobro). El pagador debe enviar exactamente ese `amount`, o el pago no se concilia con este pedido. Los `splits` se aplican sobre la entrada (USDT acreditado). **Body** ``` { "currency": "USDT", "amount": 100.00, "external_id": "PEDIDO-123", "webhook_url": "https://seusistema.com/webhooks/zypherglobal", "splits": [ { "username": "usuario1", "percentage": 10 } ] } ``` **Respuesta** ``` { "ok": true, "currency": "USDT", "network": "TRON", "deposit_address": "T...", "amount": 100.001234, "transaction_id": "PEDIDO-123", "status": "pending" } ``` El pagador envía exactamente el `amount` en USDT (TRC20) a la `deposit_address`. La confirmación llega por tu `webhook_url` cuando el pago se confirma on-chain (`status`=confirmed). Consulta también por el `transaction_id`. Consulta por el `transaction_id` en Consultar Transacciones. Splits y webhook: Splits (reparto) · Webhooks. # Retiro PIX (clave) POST `/cashout` · `"currency": "BRL"` Requisitos: la solicitud debe provenir de una dirección IPv4 y la IP debe estar configurada en `IP` de la clave. De lo contrario, la respuesta será IP no permitida. **PIX_TYPE aceptados** ``` EMAIL CPF CNPJ PHONE EVP ``` **Body** ``` { "currency": "BRL", "amount": 250.00, "name": "Fulano de Tal", "document": "00000000000", "pix_key": "fulano@banco.com", "pix_type": "EMAIL", "webhook_url": "https://seusistema.com/webhooks/zypherglobal" } ``` Formato: para CPF/CNPJ envía solo dígitos (sin puntos/guiones); para PHONE usa E.164 (+5511999999999) o solo dígitos con DDI+DDD; EMAIL debe ser un correo válido; EVP puede ser clave aleatoria (32 hex) o UUID. **Respuesta** ``` { "ok": true, "e2e": "WD_...", "amount": 250, "fee": 2.5, "status": "pending" } ``` # Retiro BR Code POST `/cashout` · `pix_copia_cola` Mismo endpoint que el cash-out por clave (`POST /cashout`), en lugar de informar `pix_key`/`pix_type`, envía el `pix_copia_cola` (BR Code / EMV) que será PAGADO. No requiere PIN (la API se autentica con la clave). Siempre en BRL. Elegibilidad: disponible solo para cuentas habilitadas para BR Code (el permiso `pix_qr_enabled`). De lo contrario la API devuelve `Retiro por BR Code no disponible para esta cuenta.` (`403`). También requiere KYC verificado, el permiso `cashout`, IP autorizada y cash-out activo. **Body** ``` { "currency": "BRL", "pix_copia_cola": "00020126580014br.gov.bcb.pix0136...5204000053039865802BR6304ABCD", "amount": 250.00, "description": "Pagamento fornecedor", "webhook_url": "https://seusistema.com/webhooks/zypherglobal" } ``` Campo `pix_copia_cola`, acepta los alias `brcode`, `emv`, `qrcode`. Valor: si el BR Code es ESTÁTICO (valor fijo incrustado), se usa ese valor; si además envías `amount` y difiere del valor fijo, la solicitud es RECHAZADA (`422 — El valor informado difiere del valor fijo del BR Code.`). Si el BR Code es DINÁMICO (sin valor incrustado), envía `amount`. **Respuesta** ``` { "ok": true, "e2e": "E20251124...", "transaction_id": "...", "fee": 2.5, "amount": 250, "status": "pending", "currency": "BRL" } ``` Errores: proveedor no elegible (`403`); KYC/permiso/IP ausentes (`403`); valor distinto del BR Code estático (`422`); saldo insuficiente; BR Code inválido/expirado. # Retiro SPEI (MXN) POST `/cashout` · `"currency": "MXN"` Para `MXN` usa la `clabe` (18 dígitos) en lugar de `pix_key`/`pix_type`. **Body** ``` { "currency": "MXN", "amount": 300.00, "name": "Juan Pérez", "document": "PEPJ800101HDFRRL09", "clabe": "012345678901234567", "webhook_url": "https://seusistema.com/webhooks/zypherglobal" } ``` **Respuesta** ``` { "ok": true, "e2e": "WL_...", "amount": 300, "fee": 3.0, "currency": "MXN", "status": "pending" } ``` # Retiro USDT POST `/cashout` · `"currency": "USDT"` Retiro USDT on-chain en la red TRON (TRC20). El `amount` es en USDT, es el monto enviado a `address`. La tarifa se cobra POR ENCIMA, en USDT (se debita de tu saldo USDT). El envío se procesa y se confirma por tu `webhook_url`. El cashout no acepta splits. **Body** ``` { "currency": "USDT", "amount": 100.00, "address": "T...", "network": "TRON", "external_id": "SAQUE-123", "webhook_url": "https://seusistema.com/webhooks/zypherglobal" } ``` **Respuesta** ``` { "ok": true, "transaction_id": "...", "amount": 100.00, "fee": 1.00, "currency": "USDT", "network": "TRON", "status": "pending" } ``` Solo TRC20: el `address` debe ser una dirección TRON válida (empieza con "T"). Red incorrecta = pérdida irreversible. El retiro queda `status`=pending hasta que el envío on-chain se confirme en tu `webhook_url`. # Splits (reparto) Los splits reparten automáticamente una parte del valor neto de un cash-in a otras cuentas de la plataforma. Envía el campo `splits` en el cuerpo del cash-in, funciona en todas las monedas habilitadas, y en los DOS modos (dinámico por valor Y clave/CLABE fija de valor abierto). **Cómo enviar** ``` "splits": [ { "username": "usuario1", "percentage": 10 }, { "username": "usuario2", "percentage": 5 } ] ``` ### Reglas - Cada ítem tiene `username` (usuario destino en la plataforma) y `percentage` (porcentaje sobre el neto). - La suma total de los porcentajes, considerando Dashboard + payload, no puede superar el 90%. Si lo supera, TODOS los splits se ignoran y el 100% del neto queda en la cuenta de origen. - Se aplican sobre el valor NETO (después de la tarifa), en el mismo instante de la confirmación del cash-in, cada destino en su propio saldo. - El cash-out NO acepta splits (solo en la entrada). ### Por moneda - BRL — reparto en reales, en el saldo BRL del destino (neto tras la tarifa PIX). - MXN — reparto en pesos, en el saldo MXN del destino (neto tras la tarifa SPEI). - USDT — el reparto es sobre el USDT acreditado, en el saldo USDT del destino (aplicado cuando la entrada on-chain se confirma). ### En los dos modos - Cobro dinámico (con `amount`): los splits valen solo para ESE pago. - Clave PIX / CLABE fija (sin `amount`): los splits quedan VINCULADOS a la clave y se aplican en cada pago que reciba. Reenviar `splits` en una nueva llamada actualiza la regla de la clave. La regla de cascada y el tope (90%) valen igual en los dos modos. Detalles de cada flujo en Cash In · Cobros. # Consultar Saldo GET `/balance` · permiso `balance` Devuelve el saldo por moneda de la cuenta autenticada. Úsalo antes de disparar un retiro para confirmar que hay liquidez. Solo se devuelven las monedas habilitadas en el sistema. **Respuesta** ``` { "ok": true, "balances": { "BRL": { "available": 1530.75, "blocked": 200.00 }, "MXN": { "available": 0.00, "blocked": 0.00 }, "USDT": { "available": 42.500000, "blocked": 0.000000 } }, "blocked_currencies": ["BRL"], "updated_at": "2026-07-24 12:00:00" } ``` ### Campos - `available` — monto GASTABLE. Es el único número que debes usar para decidir un pago. - `blocked` — monto RETENIDO (ej.: por un MED abierto). Informativo, explica por qué bajó el disponible; NO lo sumes al available. - `blocked_currencies` — monedas donde la retención es monitoreada. Hoy solo `BRL`: en MXN/USDT el `blocked` viene 0 por estructura, no porque "nada esté retenido". Nunca trates available + blocked como saldo total para pago, lo retenido no es retirable. # Convertir Moneda Convierte tu saldo entre `BRL`, `MXN`, `USDT` a la cotización actual. Flujo en dos pasos: `/conversion/simulate` devuelve la cotización sin tocar el saldo; `/conversion` ejecuta (debita el origen, acredita el destino). ### 1. Simular POST `/conversion/simulate` · permiso `cashin` **Body** ``` { "amount": 1000.00, "from": "BRL", "to": "MXN" } ``` **Respuesta** ``` { "ok": true, "rate": 3.42, "estimated_amount": 3402.90, "fee": 5.00, "from": "BRL", "to": "MXN" } ``` ### 2. Ejecutar POST `/conversion` · permiso `cashin` Mismo cuerpo que la simulación. Requiere cuenta con documentos verificados (KYC). El monto final usa la cotización del momento de la ejecución (puede diferir de la simulación). **Respuesta** ``` { "ok": true, "converted_amount": 3402.90, "rate": 3.42, "fee": 5.00, "from": "BRL", "to": "MXN", "status": "confirmed" } ``` La tarifa de conversión (`fee`) se descuenta del origen antes de aplicar la cotización. Saldo insuficiente en la moneda de origen devuelve error sin debitar nada. # Consultar Transacciones Consulta el estado REAL de cualquier transacción, cash-in o cash-out, en cualquier moneda habilitada, en el cobro dinámico (por valor) o en la clave/CLABE fija (valor abierto). Todas las rutas exigen el permiso `balance` y están acotadas a la cuenta autenticada. Prefiere recibir las confirmaciones por el `webhook_url` (webhook), es instantáneo. Usa la consulta para CONCILIAR (webhook perdido, estado indeterminado). O `/consult-transaction` é UNIVERSAL: aceita qualquer identificador (`request_number`, `transaction_id`, `e2e`, `external_id`) seja de cash-in ou de cash-out e devolve o registro certo no campo `type`. O `/consult-cashout` continua funcionando (compatível com quem já usa). ### Cash In por identificador (dinámico) Cobro dinámico (con `amount`) = un solo pago. Consulta por el `request_number` o `transaction_id` devueltos en la creación. **Consulta** ``` GET /consult-transaction?request_number=pr_... GET /consult-transaction?transaction_id=pr_... ``` **Respuesta** ``` { "ok": true, "type": "cashin", "status": "pending|confirmed|expired", "amount": 123.45, "fee": 1.23, "request_number": "pr_...", "transaction_id": "pr_...", "e2e": "E...", "provider": "Zypher Global", "updated_at": "2026-07-24 12:00:00" } ``` Vale para cualquier moneda habilitada, la moneda viene en el propio registro. ### Cash In por clave/CLABE fija (estático · LISTA) La clave PIX fija (BRL) y la CLABE fija (MXN) reciben VARIOS pagos y no tienes el `transaction_id` de cada uno. Consulta por `clabe` O por `external_id` → devuelve la LISTA de los cobros de esa clave. **Consulta** ``` GET /consult-transaction?clabe=012345678901234567 GET /consult-transaction?external_id=PEDIDO-123 GET /consult-transaction?external_id=PEDIDO-123&limit=20 ``` **Respuesta** ``` { "ok": true, "type": "cashin", "count": 2, "payments": [ { "id": "pr_...", "status": "confirmed", "amount": 100.00, "fee": 1.00, "currency": "MXN", "e2e": "E...", "transaction_id": "...", "external_id": "PEDIDO-123", "clabe": "0123...", "payer_name": "...", "created_at": "...", "updated_at": "..." } ] } ``` Para la clave PIX fija (BRL), consulta por `external_id` (no tienes CLABE). Para la CLABE fija (MXN), consulta por `clabe` o `external_id`. El `limit` limita la lista (por defecto 20, máx 100). ### Cash Out (retiro) Consulta el estado real de un retiro, esencial para no quedarte con el cash-out indeterminado si falla un webhook. Busca por uno de estos: **Parámetros (uno de ellos)** ``` GET /consult-cashout?e2e=E... GET /consult-cashout?transaction_id=pr_... GET /consult-cashout?external_id=SAQUE-123 ``` **Respuesta** ``` { "ok": true, "type": "cashout", "status": "pending|completed|failed", "amount": 250.00, "fee": 2.50, "currency": "BRL", "e2e": "E...", "transaction_id": "pr_...", "external_id": "SAQUE-123", "provider": "Zypher Global", "beneficiary": { "name": "Fulano de Tal", "document": "00000000000", "bank": "NUBANK" }, "pix_key": "fulano@banco.com", "pix_type": "EMAIL", "created_at": "2026-07-24 12:00:00", "updated_at": "2026-07-24 12:00:05", "paid_at": "2026-07-24 12:00:05" } ``` Vale para retiro en cualquier moneda habilitada, la moneda viene en `currency`. Los identificadores vienen enmascarados como referencias públicas (`pr_...`) y el `provider` muestra el nombre de la plataforma, los IDs internos nunca se exponen. # Webhooks Enviaremos un POST a tu `webhook_url` siempre que una transacción cambie de estado. Aquí está TODO sobre webhooks, cobro dinámico, clave/CLABE fija (valor abierto), cash-out y disputas (MED), en las monedas habilitadas. ### Campos comunes - `type` — `cashin` | `cashout`. - `status` — `confirmed` · `completed` · `med` · `failed`. - `currency` — moneda de la transacción (`BRL` · `MXN` · `USDT`). - `transaction_id`, `e2e` — identificadores; concilia por el `e2e` (único por pago). - `external_id` — tu identificador, cuando lo enviaste en la creación. - `origin` — presente e igual a `"static"` cuando el pago cayó en una CLAVE/CLABE FIJA (valor abierto). Ausente = cobro dinámico (por valor). El `provider` viene siempre con el nombre de la plataforma. Trata siempre el resultado por `status`, no por el mensaje. ### Cash In (confirmado) ### BRL · PIX **Cash In · BRL** ``` { "type":"cashin", "status":"confirmed", "amount":123.45, "fee":1.23, "currency":"BRL", "request_number":"pr_...", "transaction_id":"pr_...", "e2e":"E...", "provider":"Zypher Global", "updated_at":"2025-11-24 12:00:00" } ``` ### MXN · SPEI **Cash In · MXN** ``` { "type":"cashin", "status":"confirmed", "amount":500.00, "fee":5.00, "currency":"MXN", "request_number":"pr_...", "transaction_id":"pr_...", "e2e":"E...", "provider":"Zypher Global", "updated_at":"2025-11-24 12:00:00" } ``` ### USDT **Cash In · USDT (BRL→USDT)** ``` { "type":"cashin", "status":"confirmed", "amount":19.23, "fee":0.58, "currency":"USDT", "transaction_id":"CRYPTO-...", "provider":"Zypher Global", "updated_at":"2025-11-24 12:00:00" } ``` En BRL→USDT el webhook se dispara en cuanto el PIX confirma, el `amount` es el USDT ESTIMADO (el valor real se acredita al final de la conversión). ### Clave fija / valor abierto (estática) Los pagos en una CLAVE FIJA (el QR PIX fijo en BRL o la CLABE fija en MXN) llegan con `"origin":"static"` y tu `external_id` de vuelta. Como la misma clave recibe varios pagos, cada evento es individual, concilia por el `e2e`. **Cash In · clave/CLABE fija** ``` { "type":"cashin", "origin":"static", "status":"confirmed", "amount":100.00, "currency":"MXN", "external_id":"PEDIDO-123", "transaction_id":"PEDIDO-123", "e2e":"E...", "provider":"Zypher Global" } ``` Mismo formato para BRL (QR PIX fijo) y MXN (CLABE fija), solo cambia el `currency`. ### Cash Out (completado) **Cash Out · BRL** ``` { "type":"cashout", "status":"completed", "amount":250.00, "fee":2.50, "currency":"BRL", "transaction_id":"pr_...", "external_id":"SEU-ID-123", "e2e":"E...", "provider":"Zypher Global", "updated_at":"2025-11-24 12:00:00" } ``` **Cash Out · MXN** ``` { "type":"cashout", "status":"completed", "amount":300.00, "fee":3.00, "currency":"MXN", "transaction_id":"pr_...", "e2e":"E...", "provider":"Zypher Global", "updated_at":"2025-11-24 12:00:00" } ``` **Cash Out · USDT (USDT→BRL)** ``` { "type":"cashout", "status":"completed", "usdt":20.00, "brl":100.00, "fee":0.50, "currency":"USDT", "transaction_id":"pr_...", "e2e":"E...", "provider":"Zypher Global", "updated_at":"2025-11-24 12:00:00" } ``` ### Cash Out (falló / cancelado) Si un retiro ya aceptado (devuelto como `pending`) NO se completa, rechazado por el banco del receptor, clave inválida, QR vencido o sin liquidez a tiempo, enviamos un POST con `"status":"failed"` y el `fail_reason`. El importe (`amount` + `fee`) se reembolsa a tu saldo automáticamente; usa el webhook para re-acreditar o reprocesar a tu usuario final. **Cash Out · falha** ``` { "type":"cashout", "status":"failed", "amount":250.00, "fee":2.50, "transaction_id":"pr_...", "external_id":"SEU-ID-123", "e2e":"E...", "fail_reason":"Clave PIX inválida.", "updated_at":"2025-11-24 12:00:00" } ``` El `external_id` (el mismo que enviaste en el cash-out) viene en TODOS los callbacks de retiro, `completed` y `failed`, para conciliar la transacción de tu lado. El rechazo sincrónico (datos inválidos en el momento del POST) ya vuelve en el HTTP de la propia llamada; este webhook `failed` cubre el fallo asincrónico (después de `pending`). ### MED / Disputa (BRL) MED (disputa PIX): cuando una entrada ya confirmada es impugnada por el pagador, enviamos un POST con `"status":"med"` en el MISMO `transaction_id` del cobro original. Úsalo para bloquear o revertir el pedido de tu lado, el valor neto vuelve a ser debitado/retenido en tu saldo. **MED · BRL** ``` { "type": "cashin", "status": "med", "amount": 123.45, "fee": 1.23, "currency": "BRL", "request_number": "pr_...", "transaction_id": "pr_...", "e2e": "E...", "provider": "Zypher Global", "updated_at": "2025-11-24 12:00:00", "med": { "payer_name": "João da Silva", "payer_document": "***.456.789-**", "payer_institution": "ISPB 12345678", "infraction_type": "FRAUD", "infraction_status": "OPEN", "infraction_reason": "Pagador alega não reconhecer a transação", "reference_id": "PROTOCOL-123", "reported_by": "PAGADOR" } } ``` El objeto `med` trae los detalles de la infracción (cuando el adquirente los provee); los campos pueden venir vacíos/ausentes, trátalos como opcionales. Idempotencia: concilia siempre por el `e2e` (único por pago), la misma clave/CLABE fija reenvía el mismo `external_id` en pagos diferentes. Si el webhook falla, consulta el estado real en Consultar Transacciones. # Manejo de errores Toda falla de la API devuelve un cuerpo JSON estandarizado con un mensaje legible y un `error_code` estable. El estado HTTP también refleja el error (no siempre 200). Maneja los errores por `error_code`, NO cambia entre idiomas ni versiones. **Formato del error** ``` HTTP 422 { "ok": false, "error": "Chave PIX não encontrada.", "error_code": "pix_key_not_found" } ``` - `ok` — siempre `false` en caso de error. - `error` — mensaje legible (localizado por idioma). Bueno para mostrar/registrar; NO lo uses para lógica. - `error_code` — slug estable para manejar por código (switch/if). Es la fuente de la verdad. ### Estado HTTP - 400 — solicitud inválida (datos/campos). - 401 — credenciales ausentes o inválidas. - 403 — sin permiso, IP no autorizada o recurso desactivado. - 422 — dato rechazado en el procesamiento (ej.: clave no encontrada, saldo insuficiente). - 429 — demasiados intentos. Espera e inténtalo de nuevo. - 500 / 503 — error interno o servicio temporalmente no disponible. El estado también viene en el header `X-App-Error-Code` (compatibilidad con clientes que solo leen cabeceras). ### Códigos de error El mismo `error_code` puede aparecer en rutas distintas. Maneja siempre por el código. ### Autenticación y acceso - `missing_credentials`401— Client ID/Secret ausentes en la cabecera. - `invalid_credentials`401— Client ID/Secret inválidos. - `key_inactive`403— Clave de API inactiva. - `permission_denied`403— La clave no tiene el permiso requerido (cashin/cashout/balance). - `ip_not_configured`403— Cash-out sin IP en la allowlist de la clave. - `ip_not_allowed`403— IP de origen fuera de la allowlist (o no IPv4). - `invalid_account`403— Cuenta asociada a la clave inválida. - `rate_limited`429— Demasiadas solicitudes en poco tiempo. ### Clave / receptor (PIX) - `pix_key_not_found`422— La clave PIX no existe en el DICT. - `pix_key_invalid`422— Clave PIX con formato inválido. - `pix_key_type_invalid`422— Tipo de clave no aceptado para este destino. - `invalid_document`422— CPF/CNPJ inválido o no coincide con la clave. - `name_mismatch`422— Nombre/documento del receptor no coincide con la clave. - `recipient_cannot_receive`422— La cuenta de destino no puede recibir (cerrada/inactiva). - `destination_blocked`422— Clave o cuenta de destino bloqueada/suspendida. ### Monto y saldo - `insufficient_balance`400— Saldo insuficiente para el monto + la tarifa. - `amount_below_min`422— Monto por debajo del mínimo permitido. - `amount_above_limit`422— Monto por encima del límite permitido. - `amount_unavailable`422— Ningún riel cubre ese monto ahora, intenta un monto menor. - `invalid_amount`422— Monto inválido o cero. ### Procesamiento / proveedor - `duplicate`422— Operación duplicada (verifica si ya fue procesada). - `unauthorized`403— Operación no autorizada por el proveedor. - `maintenance`503— Servicio en mantenimiento. - `provider_unavailable`422— Servicio no disponible o timeout, puedes reintentar. - `cashin_failed`422— Falla genérica al generar el cobro. - `cashout_failed`422— Falla genérica en el retiro (contacta al soporte). ### Cuenta / permisos - `kyc_required`403— Cuenta sin verificación de documentos (KYC). - `currency_not_supported`400— Moneda no soportada (usa BRL, MXN, ARS, COP o USDT). - `currency_not_enabled`403— Moneda no habilitada para esta cuenta, habla con soporte. - `phone_required`422— Teléfono del comprador obligatorio en el cash-in COP (NEQUI/BRE-B). ### Conversión - `invalid_request`400— Campos amount/from/to ausentes o inválidos. - `conversion_failed`500— Falla al ejecutar la conversión (no se debitó nada). - `conversion_in_progress`409— Ya hay una conversión en curso con ese external_id, espera, no reenvíes. ### Consulta / estado - `missing_reference`400— Faltó el identificador (request_number / e2e / transaction_id). - `deposit_not_found`404— Ningún depósito para el identificador indicado. - `withdrawal_not_found`404— Ningún retiro para el identificador indicado. Reintentables: `provider_unavailable` y `rate_limited` son transitorios, puedes repetir la misma solicitud (con el mismo `external_id` para no duplicar). Los demás son definitivos: corrige el dato antes de reenviar. ### Ejemplo de manejo **JavaScript** ``` const r = await fetch(url, opts); const data = await r.json(); if (!data.ok) { switch (data.error_code) { case 'pix_key_not_found': case 'pix_key_invalid': // pedir outra chave ao usuário break; case 'insufficient_balance': // avisar saldo break; case 'provider_unavailable': case 'rate_limited': // retentar com backoff (mesmo external_id) break; default: // mostrar data.error e logar } } ```