Webhooks

Documentación de la API · Zypher Global

Generar credenciales

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.

Segurança: valide a assinatura (HMAC)

Todo POST que enviamos é assinado com HMAC-SHA256 usando o seu client_secret (o mesmo da API). Valide a assinatura ANTES de confiar no evento: assim ninguém consegue forjar um "pagamento confirmado" no seu endpoint, mesmo sabendo a sua webhook_url.

  • X-Webhook-Signature — HMAC-SHA256 do CORPO cru (compatível com integrações antigas).
  • X-Webhook-Timestamp — horário do envio (Unix, em segundos).
  • X-Webhook-Signature-V2 — HMAC-SHA256 de "{timestamp}.{corpo}" — recomendado: autentica E impede replay (reenvio de um webhook capturado).
Recomendado: valide o X-Webhook-Signature-V2 e rejeite timestamp com mais de 5 minutos. Assine sempre sobre o CORPO CRU recebido, nunca sobre o JSON re-serializado. Contas sem client_secret não recebem assinatura.
PHP · verificação
$raw    = file_get_contents('php://input');
$ts     = $_SERVER['HTTP_X_WEBHOOK_TIMESTAMP'] ?? '';
$sig    = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE_V2'] ?? '';
$secret = 'SEU_CLIENT_SECRET';

if (abs(time() - (int)$ts) > 300) { http_response_code(400); exit; }   // anti-replay (5 min)
$calc = hash_hmac('sha256', $ts . '.' . $raw, $secret);
if (!hash_equals($calc, $sig)) { http_response_code(401); exit; }      // forjado -> rejeita
// ok: webhook autentico -> processe $raw
Node.js · verificação
const crypto = require('crypto');
// use o CORPO CRU (ex.: express.raw())
const raw    = req.body;                 // Buffer/string cru, NAO o JSON ja parseado
const ts     = req.headers['x-webhook-timestamp'] || '';
const sig    = req.headers['x-webhook-signature-v2'] || '';
const secret = 'SEU_CLIENT_SECRET';

if (Math.abs(Date.now()/1000 - Number(ts)) > 300) return res.sendStatus(400);
const calc = crypto.createHmac('sha256', secret).update(ts + '.' + raw).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(calc), Buffer.from(sig))) return res.sendStatus(401);
// ok: webhook autentico

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...", "beneficiary":{ "name":"JOAO DA SILVA", "document":"***.456.789-**", "bank":"...", "pix_key":"...", "pix_type":"CPF" }, "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)

No cash-out, beneficiary traz quem recebeu: a chave PIX sempre; nome, documento e banco quando capturados na validação (DICT).

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.
¿Esta página fue útil?