Documentación de la API

Guía completa para integrar Recaudo en tus aplicaciones

Autenticación

La API de Recaudo utiliza API keys para autenticar las peticiones. Crea una cuenta para obtener tus API keys.

Importante: Nunca expongas tu Secret Key en codigo del lado del cliente. Usa la Public Key para operaciones frontend.

Tipos de API Keys

Tipo Prefijo Uso
Public Key (Live) pk_live_ Frontend, checkout forms
Secret Key (Live) sk_live_ Backend, operaciones sensibles
Public Key (Test) pk_test_ Desarrollo frontend
Secret Key (Test) sk_test_ Desarrollo backend

Autenticacion via Header

Incluye tu API key en el header Authorization como Bearer token:

curl https://recaudo.app/api/v1/customers \
  -H "Authorization: Bearer sk_live_tu_secret_key" \
  -H "Content-Type: application/json"
$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://recaudo.app/api/v1/customers', [
    'headers' => [
        'Authorization' => 'Bearer sk_live_tu_secret_key',
        'Content-Type' => 'application/json',
        'Accept' => 'application/json',
    ]
]);

$data = json_decode($response->getBody(), true);
const response = await fetch('https://recaudo.app/api/v1/customers', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer sk_live_tu_secret_key',
    'Content-Type': 'application/json',
  }
});

const data = await response.json();
import requests

response = requests.get(
    'https://recaudo.app/api/v1/customers',
    headers={
        'Authorization': 'Bearer sk_live_tu_secret_key',
        'Content-Type': 'application/json',
    }
)

data = response.json()

Manejo de Errores

La API utiliza codigos de estado HTTP convencionales para indicar el exito o fallo de una peticion.

Codigo Descripcion
200 Peticion exitosa
201 Recurso creado exitosamente
400 Peticion invalida - revisa los parametros
401 No autorizado - API key invalida o faltante
403 Prohibido - no tienes permisos para este recurso
404 Recurso no encontrado
422 Error de validacion
429 Rate limit excedido
500 Error interno del servidor

Formato de Error

Response
{
  "error": {
    "type": "validation_error",
    "code": "invalid_parameter",
    "message": "El campo email es requerido",
    "param": "email",
    "doc_url": "https://docs.recaudo.com/errors/invalid_parameter"
  }
}

Paginacion

Los endpoints que retornan listas de objetos soportan paginacion basada en cursor.

Parametros

Parametro Tipo Descripcion
limit integer Numero de resultados por pagina (default: 10, max: 100)
starting_after string ID del ultimo objeto de la pagina anterior
ending_before string ID del primer objeto de la pagina siguiente (paginacion hacia atras)

Ejemplo de Respuesta

Response
{
  "object": "list",
  "data": [...],
  "has_more": true,
  "total_count": 150
}

Clientes

Los clientes representan a tus usuarios finales. Puedes asociarles metodos de pago, suscripciones y realizar cargos.

POST /v1/customers

Crear Cliente

Crea un nuevo cliente en tu cuenta.

Parametros

Parametro Tipo Requerido Descripcion
email string Email del cliente
name string Nombre completo
phone string No Teléfono
external_id string No ID del cliente en tu sistema
tax_id string No Identificación fiscal (RFC)
address string No Dirección (calle y número). Requerido para AMEX
city string No Ciudad. Requerido para AMEX
state string No Estado o provincia. Requerido para AMEX
postal_code string No Código postal. Requerido para AMEX
country string No Código de país ISO 3166-1 alpha-2 (ej: MX, US, CO). Requerido para AMEX
metadata object No Datos adicionales (max 50 keys)
curl -X POST https://recaudo.app/api/v1/customers \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "cliente@ejemplo.com",
    "name": "Juan Pérez",
    "phone": "+52 55 1234 5678",
    "address": "Av. Reforma 123, Col. Centro",
    "city": "Ciudad de México",
    "state": "CDMX",
    "postal_code": "06600",
    "country": "MX",
    "tax_id": "XAXX010101000",
    "metadata": {
      "user_id": "usr_123"
    }
  }'
$response = $client->post('/api/v1/customers', [
    'json' => [
        'email' => 'cliente@ejemplo.com',
        'name' => 'Juan Pérez',
        'phone' => '+52 55 1234 5678',
        'address' => 'Av. Reforma 123, Col. Centro',
        'city' => 'Ciudad de México',
        'state' => 'CDMX',
        'postal_code' => '06600',
        'country' => 'MX',
        'tax_id' => 'XAXX010101000',
        'metadata' => ['user_id' => 'usr_123']
    ]
]);
const customer = await fetch('/api/v1/customers', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer sk_live_xxx',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    email: 'cliente@ejemplo.com',
    name: 'Juan Pérez',
    phone: '+52 55 1234 5678',
    address: 'Av. Reforma 123, Col. Centro',
    city: 'Ciudad de México',
    state: 'CDMX',
    postal_code: '06600',
    country: 'MX',
    tax_id: 'XAXX010101000',
    metadata: { user_id: 'usr_123' }
  })
});
response = requests.post(
    'https://recaudo.app/api/v1/customers',
    headers={'Authorization': 'Bearer sk_live_xxx'},
    json={
        'email': 'cliente@ejemplo.com',
        'name': 'Juan Pérez',
        'phone': '+52 55 1234 5678',
        'address': 'Av. Reforma 123, Col. Centro',
        'city': 'Ciudad de México',
        'state': 'CDMX',
        'postal_code': '06600',
        'country': 'MX',
        'tax_id': 'XAXX010101000',
        'metadata': {'user_id': 'usr_123'}
    }
)
Response 201
{
  "object": "customer",
  "data": {
    "id": "cus_a1b2c3d4e5f6",
    "email": "cliente@ejemplo.com",
    "name": "Juan Pérez",
    "phone": "+52 55 1234 5678",
    "address": "Av. Reforma 123, Col. Centro",
    "city": "Ciudad de México",
    "state": "CDMX",
    "postal_code": "06600",
    "country": "MX",
    "tax_id": "XAXX010101000",
    "metadata": { "user_id": "usr_123" },
    "created_at": "2024-01-15T10:30:00Z"
  }
}
GET /v1/customers

Listar Clientes

Obtiene una lista paginada de todos los clientes.

Request
curl https://recaudo.app/api/v1/customers?limit=10 \
  -H "Authorization: Bearer sk_live_xxx"
Response 200
{
  "object": "list",
  "data": [
    {
      "id": "cus_a1b2c3d4e5f6",
      "object": "customer",
      "email": "cliente@ejemplo.com",
      "name": "Juan Perez",
      ...
    }
  ],
  "has_more": true,
  "total_count": 150
}
GET /v1/customers/:id

Obtener Cliente

Obtiene los detalles de un cliente existente.

PATCH /v1/customers/:id

Actualizar Cliente

Actualiza los datos de un cliente. Solo se modifican los campos enviados.

DELETE /v1/customers/:id

Eliminar Cliente

Elimina un cliente y sus datos asociados. Esta accion no se puede deshacer.

Cargos Directos

Realiza cargos directos a metodos de pago guardados. Ideal para cargos recurrentes o one-click payments.

POST /v1/charges

Crear Cargo

Realiza un cargo a un metodo de pago guardado.

Parametros

Parametro Tipo Requerido Descripcion
amount integer Si Monto en centavos
currency string Si Codigo ISO de moneda
payment_method string Condicional* ID del método de pago guardado
token string Condicional* Token de tarjeta generado con recaudo.js (tok_xxx para Conekta, pm_xxx para Stripe)
card object No Info de tarjeta de recaudo.js: { brand, last4, exp_month, exp_year }. Recomendado para mejor tracking
customer string Condicional* ID del cliente (requerido si usa payment_method)
description string No Descripcion del cargo
Request
curl -X POST https://recaudo.app/api/v1/charges \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 25000,
    "currency": "MXN",
    "token": "tok_abc123...",
    "card": {
      "brand": "visa",
      "last4": "4242",
      "exp_month": 12,
      "exp_year": 2027
    },
    "description": "Cargo por servicio premium"
  }'
Response 201
{
  "id": "ch_abc123def456",
  "object": "charge",
  "amount": 25000,
  "currency": "MXN",
  "status": "succeeded",
  "customer_id": "cus_a1b2c3d4e5f6",
  "payment_method_id": "pm_card_xyz123",
  "description": "Cargo por servicio premium",
  "payment_intent_id": "pi_789xyz",
  "receipt_url": "https://pay.recaudo.com/receipts/ch_abc123",
  "created_at": "2024-01-15T10:30:00Z"
}
POST /v1/charges/:id/refund

Reembolsar Cargo

Realiza un reembolso total o parcial de un cargo.

Request (reembolso parcial)
curl -X POST https://recaudo.app/api/v1/charges/ch_abc123/refund \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 10000,
    "reason": "customer_request"
  }'
Response 200
{
  "id": "re_xyz789abc",
  "object": "refund",
  "amount": 10000,
  "currency": "MXN",
  "charge_id": "ch_abc123def456",
  "status": "succeeded",
  "reason": "customer_request",
  "created_at": "2024-01-15T11:00:00Z"
}

Tokenización

Tokeniza tarjetas de forma segura directamente desde tu frontend usando recaudo.js v2. La librería carga automáticamente el SDK del procesador de pagos (Conekta, Stripe) configurado en tu cuenta y tokeniza la tarjeta de manera transparente.

White-label checkout: Este sistema te permite crear tu propia página de checkout sin necesidad de redirigir a páginas externas. Los datos de tarjeta nunca pasan por tu servidor.

Integración en 4 pasos

1

Incluir la librería JavaScript

HTML
<script src="https://recaudo.app/js/recaudo.min.js"></script>
2

Inicializar la librería

Inicializa recaudo.js con tu Publishable Key. El método init() carga el SDK del procesador configurado.

JavaScript
// Crear instancia con tu Publishable Key
const recaudo = new Recaudo('pk_live_tu_publishable_key');

// Inicializar (carga el SDK del procesador automáticamente)
await recaudo.init();

// Verificar que está listo
console.log('Gateway:', recaudo.getGateway()); // 'conekta' o 'stripe'
3

Tokenizar la tarjeta

Los datos de la tarjeta se tokenizan directamente con el procesador de pagos. Nunca pasan por tu servidor.

JavaScript
async function handlePayment() {
  try {
    const token = await recaudo.createToken({
      number: '4242424242424242',
      exp_month: 12,
      exp_year: 2027,
      cvv: '123',
      name: 'Juan Pérez',
      email: 'juan@example.com',
      // Campos opcionales (requeridos para AMEX)
      address: 'Av. Reforma 123',
      city: 'CDMX',
      state: 'CDMX',
      postal_code: '06600',
      country: 'MX'
    });

    // token contiene:
    // - id: Token del procesador (tok_xxx para Conekta, pm_xxx para Stripe)
    // - gateway: 'conekta' o 'stripe'
    // - card: { brand, last4, exp_month, exp_year }

    // Enviar al backend para procesar
    await fetch('/tu-backend/procesar-pago', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        token: token.id,
        card: token.card,  // Incluir info de tarjeta para mejor tracking
        amount: 10000
      })
    });

  } catch (error) {
    // error.type: 'validation_error', 'gateway_error', 'sdk_error'
    console.error('Error:', error.message);
  }
}
4

Procesar el cobro desde tu backend

Usa el token para crear un cargo o una suscripción. Incluye el objeto card para mejor tracking.

cURL - Cargo directo
curl -X POST https://recaudo.app/api/v1/charges \
  -H "Authorization: Bearer sk_live_tu_secret_key" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 10000,
    "currency": "MXN",
    "token": "tok_abc123...",
    "card": {
      "brand": "visa",
      "last4": "4242",
      "exp_month": 12,
      "exp_year": 2027
    },
    "description": "Compra en tienda",
    "receipt_email": "cliente@example.com"
  }'
cURL - Crear suscripción
curl -X POST https://recaudo.app/api/v1/subscriptions \
  -H "Authorization: Bearer sk_live_tu_secret_key" \
  -H "Content-Type: application/json" \
  -d '{
    "plan": "plan_uuid_here",
    "customer": "cus_uuid_here",
    "token": "tok_abc123...",
    "card": {
      "brand": "visa",
      "last4": "4242",
      "exp_month": 12,
      "exp_year": 2027
    }
  }'

Objeto Token (respuesta de createToken)

Respuesta de createToken()
{
  "id": "tok_2sR5Vx1J5K...",  // Token del procesador (tok_xxx Conekta, pm_xxx Stripe)
  "gateway": "conekta",       // Procesador utilizado
  "card": {
    "brand": "visa",
    "last4": "4242",
    "exp_month": 12,
    "exp_year": 2027
  },
  "created_at": "2024-01-15T10:30:00.000Z"
}

Endpoints de la API

Método Endpoint Descripción
GET /v1/tokens/config Obtener configuración del procesador (usado por recaudo.js para cargar el SDK correcto)
GET /v1/tokens/public-key Obtener clave pública para encriptación (legacy v1)
POST /v1/tokens Crear token desde datos encriptados (legacy v1)
GET /v1/tokens/:token Obtener información de un token
DELETE /v1/tokens/:token Eliminar un token

Métodos de recaudo.js

Método Descripción
new Recaudo(publishableKey) Constructor. Crea una instancia con tu Publishable Key.
init() Carga el SDK del procesador configurado. Debe llamarse antes de createToken().
createToken(cardData) Tokeniza los datos de la tarjeta con el procesador.
getGateway() Retorna el nombre del procesador ('conekta', 'stripe').
isInitialized() Retorna true si init() ya fue llamado.
detectCardBrand(number) Detecta la marca de la tarjeta (visa, mastercard, amex, etc).
Seguridad: Los tokens son generados directamente por el procesador de pagos (Conekta/Stripe). Los datos de tarjeta nunca pasan por los servidores de Recaudo ni por tu servidor, facilitando el cumplimiento de PCI DSS.

Manejo de Errores

Los errores de recaudo.js incluyen un tipo para facilitar su manejo:

Ejemplo de manejo de errores
try {
  await recaudo.init();
  const token = await recaudo.createToken(cardData);
} catch (error) {
  switch (error.type) {
    case 'validation_error':
      // Datos de tarjeta inválidos
      showError(error.message);
      break;
    case 'gateway_error':
      // Error del procesador (tarjeta rechazada, etc)
      showError(error.message);
      break;
    case 'sdk_error':
      // Error cargando el SDK del procesador
      showError('Error de conexión. Intenta de nuevo.');
      break;
    case 'configuration_error':
      // Procesador no configurado
      showError('Error de configuración.');
      break;
    default:
      showError('Error inesperado.');
  }
}

Suscripciones

Las suscripciones te permiten cobrar de forma recurrente a tus clientes. Soportan periodos de prueba, pausas y cambios de plan.

POST /v1/subscriptions

Crear Suscripción

Crea una nueva suscripción para un cliente. Puedes usar un método de pago almacenado o un token de recaudo.js.

Tokenización: Puedes usar el parámetro token con un token de recaudo.js (tok_xxx) en lugar de payment_method. El token será convertido automáticamente en un método de pago almacenado.

Parámetros

Parámetro Tipo Requerido Descripción
customer string (uuid) ID del cliente
plan string (uuid) ID del plan de suscripción
payment_method string (uuid) No* ID de método de pago almacenado. Requerido si no hay trial y no se usa token.
token string No* Token de recaudo.js (tok_xxx para Conekta, pm_xxx para Stripe). Alternativa a payment_method.
card object No Info de tarjeta de recaudo.js: { brand, last4, exp_month, exp_year }. Recomendado para mejor tracking
trial_days integer No Días de prueba (0-365)
amount numeric No Monto personalizado (override del plan)
metadata object No Datos adicionales
curl -X POST https://recaudo.app/api/v1/subscriptions \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "customer": "550e8400-e29b-41d4-a716-446655440000",
    "plan": "660e8400-e29b-41d4-a716-446655440001",
    "token": "tok_abc123def456",
    "card": {
      "brand": "visa",
      "last4": "4242",
      "exp_month": 12,
      "exp_year": 2027
    },
    "metadata": {
      "signup_source": "landing_page"
    }
  }'
curl -X POST https://recaudo.app/api/v1/subscriptions \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "customer": "550e8400-e29b-41d4-a716-446655440000",
    "plan": "660e8400-e29b-41d4-a716-446655440001",
    "payment_method": "770e8400-e29b-41d4-a716-446655440002",
    "metadata": {
      "signup_source": "landing_page"
    }
  }'
Response 201
{
  "id": "sub_1a2b3c4d5e6f",
  "object": "subscription",
  "status": "active",
  "customer_id": "cus_a1b2c3d4e5f6",
  "plan": {
    "id": "plan_premium_monthly",
    "name": "Premium Mensual",
    "amount": 29900,
    "currency": "MXN",
    "interval": "month"
  },
  "current_period_start": "2024-01-15T00:00:00Z",
  "current_period_end": "2024-02-15T00:00:00Z",
  "trial_end": null,
  "cancel_at_period_end": false,
  "created_at": "2024-01-15T10:30:00Z"
}

Acciones de Suscripcion

POST /v1/subscriptions/:id/cancel Cancelar suscripcion
curl -X POST https://recaudo.app/api/v1/subscriptions/sub_1a2b3c/cancel \
  -H "Authorization: Bearer sk_live_xxx" \
  -d '{"cancel_at_period_end": true, "reason": "too_expensive"}'
POST /v1/subscriptions/:id/pause Pausar suscripcion
curl -X POST https://recaudo.app/api/v1/subscriptions/sub_1a2b3c/pause \
  -H "Authorization: Bearer sk_live_xxx"
POST /v1/subscriptions/:id/resume Reanudar suscripcion
curl -X POST https://recaudo.app/api/v1/subscriptions/sub_1a2b3c/resume \
  -H "Authorization: Bearer sk_live_xxx"

Facturas

Las facturas permiten gestionar cobros con soporte completo para parcialidades (pagos en cuotas), políticas de moratorios y seguimiento detallado de pagos. Ideal para colegiaturas, créditos y servicios recurrentes.

Endpoints Disponibles

GET /v1/invoices Listar facturas
POST /v1/invoices Crear factura (con o sin parcialidades)
GET /v1/invoices/:id Obtener factura
PATCH /v1/invoices/:id Actualizar factura
DELETE /v1/invoices/:id Eliminar factura
POST /v1/invoices/:id/pay Marcar como pagada
POST /v1/invoices/:id/void Anular factura
GET /v1/invoices/:id/installments Listar parcialidades
PATCH /v1/invoices/:id/installments/:installment_id Actualizar parcialidad

Crear Factura

Crea una factura con items y opcionalmente configura parcialidades automáticas o manuales.

curl -X POST https://recaudo.app/api/v1/invoices \
  -H "Authorization: Bearer sk_live_tu_secret_key" \
  -H "Content-Type: application/json" \
  -d '{
    "customer_id": "cus_abc123",
    "document_date": "2024-01-15",
    "items": [
      {
        "description": "Colegiatura Enero 2024",
        "quantity": 1,
        "unit_price": 5000.00
      }
    ],
    "has_installments": true,
    "installment_count": 3,
    "installment_frequency": "monthly",
    "first_installment_due_date": "2024-01-20"
  }'
$response = $client->post('https://recaudo.app/api/v1/invoices', [
    'headers' => [
        'Authorization' => 'Bearer sk_live_tu_secret_key',
        'Content-Type' => 'application/json',
    ],
    'json' => [
        'customer_id' => 'cus_abc123',
        'document_date' => '2024-01-15',
        'items' => [
            [
                'description' => 'Colegiatura Enero 2024',
                'quantity' => 1,
                'unit_price' => 5000.00,
            ]
        ],
        'has_installments' => true,
        'installment_count' => 3,
        'installment_frequency' => 'monthly',
        'first_installment_due_date' => '2024-01-20',
    ]
]);
const response = await fetch('https://recaudo.app/api/v1/invoices', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer sk_live_tu_secret_key',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    customer_id: 'cus_abc123',
    document_date: '2024-01-15',
    items: [
      {
        description: 'Colegiatura Enero 2024',
        quantity: 1,
        unit_price: 5000.00
      }
    ],
    has_installments: true,
    installment_count: 3,
    installment_frequency: 'monthly',
    first_installment_due_date: '2024-01-20'
  })
});
response = requests.post(
    'https://recaudo.app/api/v1/invoices',
    headers={
        'Authorization': 'Bearer sk_live_tu_secret_key',
        'Content-Type': 'application/json',
    },
    json={
        'customer_id': 'cus_abc123',
        'document_date': '2024-01-15',
        'items': [
            {
                'description': 'Colegiatura Enero 2024',
                'quantity': 1,
                'unit_price': 5000.00
            }
        ],
        'has_installments': True,
        'installment_count': 3,
        'installment_frequency': 'monthly',
        'first_installment_due_date': '2024-01-20'
    }
)

Parámetros de Parcialidades

Parámetro Tipo Descripción
has_installments boolean Habilitar parcialidades
installment_count integer Número de parcialidades (2-48)
installment_frequency string weekly, biweekly o monthly
first_installment_due_date date Fecha de vencimiento de la primera parcialidad
late_fee_policy_id uuid ID de política de moratorios

Parcialidades Manuales

Para mayor control, puedes definir cada parcialidad manualmente con sus propias fechas, montos y estado de pago. Útil para importar facturas existentes con pagos parciales.

Manual Installments Example
{
  "customer_id": "cus_abc123",
  "items": [
    { "description": "Servicio anual", "quantity": 1, "unit_price": 12000.00 }
  ],
  "installments": [
    {
      "due_date": "2024-01-15",
      "amount": 4000.00,
      "external_id": "FACT-001-P1",
      "is_paid": true,
      "amount_paid": 4000.00,
      "paid_at": "2024-01-10"
    },
    {
      "due_date": "2024-02-15",
      "amount": 4000.00,
      "external_id": "FACT-001-P2",
      "is_paid": false
    },
    {
      "due_date": "2024-03-15",
      "amount": 4000.00,
      "external_id": "FACT-001-P3",
      "is_paid": false
    }
  ]
}

Objeto Invoice

Invoice Object with Installments
{
  "id": "inv_abc123xyz",
  "object": "invoice",
  "invoice_number": "INV-2024-0001",
  "external_id": "FACT-001",
  "status": "partial",
  "customer_id": "cus_a1b2c3d4e5f6",
  "currency": "MXN",
  "subtotal": 5000.00,
  "tax_amount": 0,
  "total": 5000.00,
  "amount_paid": 1666.67,
  "amount_due": 3333.33,
  "document_date": "2024-01-15",
  "due_date": "2024-03-20",
  "has_installments": true,
  "installment_count": 3,
  "installment_frequency": "monthly",
  "items": [
    {
      "id": "ii_xyz789",
      "description": "Colegiatura Enero 2024",
      "quantity": 1,
      "unit_price": 5000.00,
      "total": 5000.00
    }
  ],
  "installments": [
    {
      "id": "inst_001",
      "installment_number": 1,
      "status": "paid",
      "principal_amount": 1666.67,
      "late_fee_amount": 0,
      "total_amount": 1666.67,
      "amount_paid": 1666.67,
      "amount_due": 0,
      "due_date": "2024-01-20",
      "paid_at": "2024-01-18T14:30:00Z",
      "days_overdue": 0,
      "external_id": null
    },
    {
      "id": "inst_002",
      "installment_number": 2,
      "status": "pending",
      "principal_amount": 1666.67,
      "late_fee_amount": 0,
      "total_amount": 1666.67,
      "amount_paid": 0,
      "amount_due": 1666.67,
      "due_date": "2024-02-20",
      "paid_at": null,
      "days_overdue": 0,
      "external_id": null
    },
    {
      "id": "inst_003",
      "installment_number": 3,
      "status": "pending",
      "principal_amount": 1666.66,
      "late_fee_amount": 0,
      "total_amount": 1666.66,
      "amount_paid": 0,
      "amount_due": 1666.66,
      "due_date": "2024-03-20",
      "paid_at": null,
      "days_overdue": 0,
      "external_id": null
    }
  ],
  "created_at": "2024-01-15T00:00:00Z"
}

Estados de Parcialidades

Estado Descripción
pending Pendiente de pago, no vencida
partial Pago parcial recibido
paid Completamente pagada
overdue Vencida sin pagar (puede generar moratorios)
cancelled Cancelada (factura anulada)

Moratorios

Las parcialidades vencidas pueden generar moratorios automáticamente según la política configurada. El campo late_fee_amount muestra el moratorio acumulado y se suma al total_amount de la parcialidad.

Importante: Los moratorios se calculan sobre el saldo pendiente del principal, no sobre moratorios previos (evita interés compuesto no deseado).

Webhooks

Los webhooks te permiten recibir notificaciones en tiempo real sobre eventos en tu cuenta. Configura un endpoint HTTPS y recibe actualizaciones automaticas.

Tipos de Eventos

Pagos

  • payment.created
  • payment.succeeded
  • payment.failed
  • payment.refunded

Links de Pago

  • payment_link.created
  • payment_link.paid
  • payment_link.expired

Suscripciones

  • subscription.created
  • subscription.updated
  • subscription.canceled
  • subscription.paused
  • subscription.resumed
  • subscription.trial_ending

Facturas

  • invoice.created
  • invoice.paid
  • invoice.payment_failed
  • invoice.upcoming

Formato del Payload

Webhook Payload
{
  "id": "evt_1a2b3c4d5e6f",
  "object": "event",
  "type": "payment.succeeded",
  "api_version": "2024-01-01",
  "created": 1705312200,
  "data": {
    "object": {
      "id": "pi_abc123xyz",
      "object": "payment_intent",
      "amount": 50000,
      "currency": "MXN",
      "status": "succeeded",
      "customer_id": "cus_a1b2c3d4e5f6",
      ...
    }
  }
}

Verificacion de Firma (HMAC)

Cada webhook incluye un header X-Recaudo-Signature que debes verificar para asegurar que el mensaje viene de Recaudo.

<?php
// Obtener el payload y la firma
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_RECAUDO_SIGNATURE'];
$secret = 'whsec_tu_webhook_secret';

// Extraer timestamp y firma del header
// Formato: t=timestamp,v1=signature
$parts = explode(',', $signature);
$timestamp = substr($parts[0], 2);
$receivedSignature = substr($parts[1], 3);

// Calcular la firma esperada
$signedPayload = $timestamp . '.' . $payload;
$expectedSignature = hash_hmac('sha256', $signedPayload, $secret);

// Verificar
if (hash_equals($expectedSignature, $receivedSignature)) {
    // Firma valida - procesar el evento
    $event = json_decode($payload, true);

    switch ($event['type']) {
        case 'payment.succeeded':
            // Manejar pago exitoso
            break;
        case 'subscription.canceled':
            // Manejar cancelacion
            break;
    }

    http_response_code(200);
} else {
    // Firma invalida
    http_response_code(400);
}
const crypto = require('crypto');

app.post('/webhooks/recaudo', express.raw({type: 'application/json'}), (req, res) => {
  const payload = req.body;
  const signature = req.headers['x-recaudo-signature'];
  const secret = 'whsec_tu_webhook_secret';

  // Extraer timestamp y firma
  const parts = signature.split(',');
  const timestamp = parts[0].split('=')[1];
  const receivedSignature = parts[1].split('=')[1];

  // Calcular firma esperada
  const signedPayload = `${timestamp}.${payload}`;
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');

  // Verificar
  if (crypto.timingSafeEqual(
    Buffer.from(expectedSignature),
    Buffer.from(receivedSignature)
  )) {
    const event = JSON.parse(payload);

    switch (event.type) {
      case 'payment.succeeded':
        // Manejar pago exitoso
        break;
      case 'subscription.canceled':
        // Manejar cancelacion
        break;
    }

    res.status(200).send('OK');
  } else {
    res.status(400).send('Invalid signature');
  }
});
import hmac
import hashlib
from flask import Flask, request

app = Flask(__name__)

@app.route('/webhooks/recaudo', methods=['POST'])
def webhook():
    payload = request.data
    signature = request.headers.get('X-Recaudo-Signature')
    secret = 'whsec_tu_webhook_secret'

    # Extraer timestamp y firma
    parts = signature.split(',')
    timestamp = parts[0].split('=')[1]
    received_signature = parts[1].split('=')[1]

    # Calcular firma esperada
    signed_payload = f"{timestamp}.{payload.decode()}"
    expected_signature = hmac.new(
        secret.encode(),
        signed_payload.encode(),
        hashlib.sha256
    ).hexdigest()

    # Verificar
    if hmac.compare_digest(expected_signature, received_signature):
        event = request.json

        if event['type'] == 'payment.succeeded':
            # Manejar pago exitoso
            pass
        elif event['type'] == 'subscription.canceled':
            # Manejar cancelacion
            pass

        return 'OK', 200
    else:
        return 'Invalid signature', 400

Mejores Practicas

  • Siempre verifica la firma HMAC antes de procesar el evento
  • Responde con 200 rapidamente y procesa de forma asincrona
  • Implementa idempotencia usando el ID del evento
  • Usa HTTPS con un certificado valido
  • Los webhooks se reintentan automaticamente si fallas (hasta 3 veces)

Flujos de Conversación

La API de Flujos de Conversación te permite desplegar flujos automatizados de WhatsApp a tus clientes, similar a como funcionan las plataformas de engagement como Treble. Puedes iniciar campañas masivas, monitorear el estado de las ejecuciones y actualizar variables en tiempo real.

GET /api/v1/flows

Lista todos los flujos de conversación disponibles para tu tenant.

Respuesta

{
  "object": "list",
  "data": [
    {
      "id": "01234567-89ab-cdef-0123-456789abcdef",
      "name": "Flujo de Cobranza",
      "description": "Recordatorio de pago con opciones",
      "type": "collection",
      "purpose": "Recordatorio",
      "channel": "whatsapp",
      "status": "published",
      "version": 1,
      "total_runs": 150,
      "total_responses": 89,
      "response_rate": 59.3,
      "published_at": "2024-01-15T10:30:00Z",
      "created_at": "2024-01-10T08:00:00Z",
      "updated_at": "2024-01-15T10:30:00Z"
    }
  ],
  "url": "/v1/flows"
}
POST /api/v1/flows/{flow_id}/deploy

Despliega un flujo a uno o más destinatarios. Cada destinatario recibirá el flujo de forma independiente. Puedes incluir variables personalizadas que se usarán para reemplazar placeholders en los mensajes.

Cuerpo de la Petición

{
  "recipients": [
    {
      "phone": "+5215512345678",
      "variables": {
        "nombre": "Juan Pérez",
        "monto": "$1,500.00",
        "fecha_limite": "15 de febrero"
      }
    },
    {
      "phone": "+5215587654321",
      "variables": {
        "nombre": "María García",
        "monto": "$2,300.00",
        "fecha_limite": "20 de febrero"
      }
    }
  ],
  "scheduled_at": null  // Opcional: programar para después
}

Respuesta

{
  "success": true,
  "message": "Flow deployed to 2 recipients",
  "data": {
    "flow_id": "01234567-89ab-cdef-0123-456789abcdef",
    "batch_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "total_recipients": 2,
    "runs_created": 2,
    "scheduled_at": null,
    "run_ids": [
      "f1a2b3c4-d5e6-7890-abcd-111111111111",
      "f1a2b3c4-d5e6-7890-abcd-222222222222"
    ]
  }
}
GET /api/v1/flows/{flow_id}/runs

Lista las ejecuciones de un flujo con filtros opcionales.

Parámetros de Query

Parámetro Tipo Descripción
status string Filtrar por estado: pending, running, waiting, completed, failed, cancelled
batch_id string Filtrar por ID de lote (retornado en el deploy)
phone string Filtrar por número de teléfono del destinatario
limit integer Resultados por página (default 20)

Respuesta

{
  "object": "list",
  "data": [
    {
      "id": "f1a2b3c4-d5e6-7890-abcd-111111111111",
      "flow_id": "01234567-89ab-cdef-0123-456789abcdef",
      "flow_name": "Flujo de Cobranza",
      "phone_number": "+5215512345678",
      "country_code": "MX",
      "customer_id": "cust-uuid-123",
      "customer_name": "Juan Pérez",
      "conversation_id": "conv-uuid-789",
      "status": "completed",
      "source": "api",
      "batch_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "context": {
        "nombre": "Juan Pérez",
        "monto": "$1,500.00"
      },
      "current_node_id": null,
      "error_message": null,
      "scheduled_at": null,
      "started_at": "2024-01-20T10:00:00Z",
      "finished_at": "2024-01-20T10:05:30Z",
      "created_at": "2024-01-20T09:59:58Z"
    }
  ],
  "has_more": false,
  "url": "/v1/flows/01234567-89ab-cdef-0123-456789abcdef/runs"
}
GET /api/v1/flows/{flow_id}/runs/{run_id}

Obtiene el detalle de una ejecución específica del flujo.

Respuesta

{
  "object": "flow_run",
  "id": "f1a2b3c4-d5e6-7890-abcd-111111111111",
  "flow_id": "01234567-89ab-cdef-0123-456789abcdef",
  "flow_name": "Flujo de Cobranza",
  "phone_number": "+5215512345678",
  "country_code": "MX",
  "customer_id": "cust-uuid-123",
  "customer_name": "Juan Pérez",
  "conversation_id": "conv-uuid-789",
  "status": "waiting",
  "source": "api",
  "batch_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "context": {
    "nombre": "Juan Pérez",
    "monto": "$1,500.00",
    "respuesta_cliente": "Sí, pagaré mañana"
  },
  "current_node_id": "node_1768811907821_4wexo4g3h",
  "error_message": null,
  "scheduled_at": null,
  "started_at": "2024-01-20T10:00:00Z",
  "finished_at": null,
  "created_at": "2024-01-20T09:59:58Z"
}
POST /api/v1/flows/{flow_id}/runs/{run_id}/variables

Actualiza las variables de una ejecución en curso. Útil para inyectar información externa que será usada en los siguientes nodos del flujo.

Cuerpo de la Petición

{
  "variables": {
    "crm_status": "contacted",
    "agent_notes": "Cliente confirmó pago para mañana",
    "follow_up_date": "2024-01-21"
  }
}
POST /api/v1/flows/{flow_id}/runs/{run_id}/cancel

Cancela una ejecución que está pendiente o en espera. Las ejecuciones completadas o fallidas no pueden cancelarse.

Respuesta

{
  "success": true,
  "message": "Flow run cancelled successfully",
  "data": {
    "id": "f1a2b3c4-d5e6-7890-abcd-111111111111",
    "flow_id": "01234567-89ab-cdef-0123-456789abcdef",
    "flow_name": "Flujo de Cobranza",
    "phone_number": "+5215512345678",
    "country_code": "MX",
    "customer_id": "cust-uuid-123",
    "customer_name": "Juan Pérez",
    "conversation_id": null,
    "status": "cancelled",
    "source": "api",
    "batch_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "context": {
      "nombre": "Juan Pérez"
    },
    "current_node_id": null,
    "error_message": null,
    "scheduled_at": null,
    "started_at": null,
    "finished_at": "2024-01-20T11:00:00Z",
    "created_at": "2024-01-20T09:59:58Z"
  }
}

Webhooks de Conversación

Configura webhooks para recibir notificaciones en tiempo real sobre eventos de conversación y flujos. Los webhooks se configuran desde el panel de Webhook Endpoints.

Eventos Disponibles

Evento Descripción
conversation.created Nueva conversación iniciada
conversation.assigned Conversación asignada a un agente
conversation.transferred Conversación transferida a otro agente o grupo
conversation.resolved Conversación marcada como resuelta
conversation.closed Conversación cerrada con motivo
conversation.message.received Mensaje recibido del cliente
conversation.message.sent Mensaje enviado al cliente
flow_run.started Ejecución de flujo iniciada
flow_run.completed Ejecución de flujo completada
flow_run.failed Ejecución de flujo fallida
flow_run.waiting Flujo esperando respuesta del cliente

Ejemplo de Payload (conversation.message.received)

{
  "id": "evt_123456",
  "type": "conversation.message.received",
  "created_at": "2024-01-20T10:05:30Z",
  "data": {
    "id": "msg_abc123",
    "conversation_id": "conv_xyz789",
    "direction": "inbound",
    "type": "text",
    "body": "Sí, haré el pago mañana",
    "sender_identifier": "+5215512345678",
    "created_at": "2024-01-20T10:05:30Z",
    "conversation": {
      "id": "conv_xyz789",
      "channel": "whatsapp",
      "channel_identifier": "+5215512345678",
      "status": "open",
      "customer_id": "cust_abc",
      "customer_name": "Juan Pérez",
      "assigned_user_id": "user_123",
      "assigned_user_name": "Ana López"
    }
  }
}

Casos de Uso

Campaña de Cobranza Masiva

Despliega un flujo de recordatorio de pago a miles de clientes con variables personalizadas (nombre, monto, fecha). El flujo puede incluir múltiples mensajes, esperas por respuesta, y ramificaciones basadas en las respuestas del cliente.

Integración con CRM

Usa los webhooks para sincronizar el estado de las conversaciones con tu CRM. Cuando un cliente responde, actualiza automáticamente su registro con la respuesta y programa seguimientos.

Encuestas y NPS

Despliega flujos interactivos con botones para recopilar feedback de clientes. Usa las variables del run para almacenar las respuestas y consultar los resultados vía API.