transferfix.

Integraciones

Webhooks

Transferfix envía eventos firmados a tu endpoint cada vez que una transferencia impacta el estado de una orden. Registrás la URL y el secret una sola vez al crear la cuenta y nosotros nos encargamos del resto.

Webhooks salientesHMAC SHA-256payment.paid · payment.overpaid · payment.partially_paid

Configuración — sin panel

No hay panel de configuración. Enviás webhook_url y webhook_secret la primera vez que creás una cuenta y queda registrado para toda tu empresa.

1. Generá un secret

Secret aleatorio de al menos 32 caracteres. Guardalo en tu backend como variable de entorno.

2. Enviá los campos al crear

Incluí webhook_url + webhook_secret en POST /api/v2/accounts. Se registra una vez y se reutiliza.

3. Manejá los eventos

Transferfix firmará cada request con HMAC-SHA256 y reintentará hasta 6 veces ante fallas.
Crear cuenta con registro de webhook
curl -X POST "https://transferfix.com.ar/api/v2/accounts" \
  -H "X-API-Key: ck_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "customer_email": "buyer@example.com",
    "total_amount": 12500,
    "currency": "ARS",
    "order_reference": "PIANTAO-ABC",
    "webhook_url": "https://yourapp.com/api/webhooks/transferfix",
    "webhook_secret": "at-least-32-chars-random-hex-string"
  }'

Rotación

Para rotar el secret, enviá un nuevo par webhook_url/webhook_secret en el próximo POST /api/v2/accounts. La rotación es instantánea — entregas en curso aún pueden firmar con el secret viejo, por eso conviene aceptar ambos durante la ventana de rollout.

Eventos

Cada cambio relevante dispara un evento.

payment.paid

Orden pagada exactamente por el monto total.

payment.overpaid

El cliente transfirió más que el total_amount. Revisá o aceptá según tu lógica.

payment.partially_paid

Llega una transferencia parcial. Pueden venir múltiples antes de payment.paid.

Headers y payload

Headers del request
POST {your_webhook_url}
Content-Type: application/json
User-Agent: Transferfix-Webhook/1.0
X-Transferfix-Event: payment.paid
X-Transferfix-Delivery-Id: 01J5Z...
X-Transferfix-Timestamp: 1745245680
X-Transferfix-Signature: sha256=4a9f...
Cuerpo del request
{
  "event": "payment.paid",
  "delivery_id": "01J5Z...",
  "account_id": 1234,
  "order_reference": "api_PIANTAO-ABC",
  "client_order_reference": "PIANTAO-ABC",
  "metadata": { "event_id": "rock-2024", "seat": "F12" },
  "customer_email": "buyer@example.com",
  "cucuru_alias": "pianta.entrada-0014",
  "cucuru_collection_id": "bf3f72c0-9b8c-4018-8f0a-8a17c52b1cec",
  "amount_paid": 12500,
  "total_amount": 12500,
  "payment_amount": 12500,
  "currency": "ARS",
  "status": "PAID",
  "alias_payment_count": 1,
  "is_first_payment": true,
  "timestamp": "2026-04-21T18:48:00.000Z"
}

Verificación de firma

La firma se calcula sobre `${timestamp}.${raw_body}` con HMAC-SHA256 y se envía en hex minúscula como `sha256=...`.

Node.js
import { createHmac, timingSafeEqual } from 'crypto'

function verifyTransferfixSignature(
  rawBody: string,
  headers: Record<string, string>,
  secret: string
): boolean {
  const ts = headers['x-transferfix-timestamp']
  const given = (headers['x-transferfix-signature'] || '').replace(/^sha256=/, '')
  if (!ts || !given) return false

  const age = Math.floor(Date.now() / 1000) - parseInt(ts, 10)
  if (Math.abs(age) > 300) return false // reject > 5 min

  const expected = createHmac('sha256', secret)
    .update(`${ts}.${rawBody}`)
    .digest('hex')

  try {
    return timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(given, 'hex'))
  } catch {
    return false
  }
}
PHP
<?php
function verify_transferfix_signature($raw_body, $headers, $secret) {
  $ts = $headers['X-Transferfix-Timestamp'] ?? '';
  $given = str_replace('sha256=', '', $headers['X-Transferfix-Signature'] ?? '');
  if (!$ts || !$given) return false;

  if (abs(time() - intval($ts)) > 300) return false;

  $expected = hash_hmac('sha256', "$ts.$raw_body", $secret);
  return hash_equals($expected, $given);
}

Reglas

Verificá en constant-time (timingSafeEqual / hash_equals). Rechazá timestamps con más de 5 minutos de diferencia para prevenir replay. Leé el body crudo — no parsees JSON antes de firmar.

Política de reintentos

6 intentos sobre ~20 horas. Reintentos sólo ante errores de red, 5xx, 408 y 429.

IntentoDelay hasta siguiente
11 min
25 min
330 min
42 h
56 h
612 h (final)

Requisitos de la respuesta

Respondé 2xx dentro de 10 segundos. Cualquier 4xx (excepto 408/429) se trata como error de cliente permanente y la entrega queda en DEAD. Para operaciones lentas, respondé 2xx rápido y encolá el trabajo real en background.

Idempotencia

Cada entrega trae X-Transferfix-Delivery-Id. En reintentos es el mismo.

Patrón del handler

Guardá el delivery_id en una tabla con unique constraint y cortá si ya lo viste. También podés usar cucuru_collection_id para deduplicar por pago en el banco.

Interno: banco → Transferfix

Contexto operacional. Esto NO es algo que tu backend consume — es cómo nos llega la notificación del banco.

POST/api/v1/webhooks/collection_received

Endpoint interno. El banco nos llama, procesamos el pago y emitimos el webhook saliente a tu URL.

Payload del banco (interno, para contexto)
{
  "collection_id": "bf3f72c0-9b8c-4018-8f0a-8a17c52b1cec",
  "customer_id": "pianta.entrada-0014",
  "amount": 12500,
  "currency_id": "ARS"
}
Transferfix API Docs