Volver a Productos
Referencia API

Webhooks

Recibe notificaciones HTTP en tiempo real cuando el estado de una factura cambia. Configura un endpoint en tu servidor y nosotros lo llamamos automáticamente.

¿Cómo funciona?

Cuando configuras un Webhook URL en un servicio, el sistema envía una petición HTTP POST a esa URL cada vez que una factura de ese servicio cambia de estado. No necesitas consultar la API periódicamente — los eventos llegan automáticamente.

ℹ️
Un webhook por servicio. Cada servicio tiene su propia URL y su propio webhook_secret independiente. Puedes usar el mismo endpoint para múltiples servicios si verificas el campo serviceId del payload.

El flujo completo:

  1. Configuras tu Webhook URL en Servicios → Editar servicio.
  2. Una factura cambia de estado (pagada, vencida, cancelada, etc.).
  3. Enviamos un POST a tu URL con el payload JSON y las cabeceras de seguridad.
  4. Tu servidor verifica la firma HMAC y procesa el evento.
  5. Responde con cualquier código 2xx para confirmar la recepción.

Eventos disponibles

El campo event del payload (y el header X-Event) indica qué ocurrió:

invoice.created
Se creó una nueva factura (manualmente o desde una plantilla).
invoice.paid
La factura fue marcada como pagada (vía Stripe, PayPal, confirmación manual, o cambio de estado).
invoice.overdue
La fecha de vencimiento pasó sin pago. El sistema actualiza el estado automáticamente cada día.
invoice.cancelled
La factura fue cancelada.
invoice.pending
La factura volvió al estado pendiente (ej.: después de rechazar un pago manual).

Estructura del payload

El cuerpo de la petición es JSON con codificación UTF-8. Los campos con valor null se omiten.

JSON
{
  "event":            "invoice.paid",
  "timestamp":        "2026-07-15T18:32:00Z",
  "invoice": {
    "invoiceId":        "BILLING-2026-000042",
    "serviceName":     "billing-core",        // código del servicio
    "serviceId":       3,
    "amount":          150.00,               // total a pagar
    "subtotal":        130.00,
    "taxAmount":       19.50,
    "retentionAmount": 0.00,
    "taxMode":         "global",             // "none" | "global" | "per_item"
    "taxRate":         15,
    "currency":        "USD",                // "USD" | "NIO"
    "status":          "PAID",               // PENDING | PAID | OVERDUE | CANCELLED
    "customerId":      12,
    "customerName":    "Empresa S.A.",
    "issueDate":       "2026-07-01T00:00:00Z",
    "dueDate":         "2026-07-31T23:59:59Z",
    "notes":           "Factura mensual julio",
    "items": [
      {
        "description": "Plan Pro",
        "quantity":    1,
        "unitPrice":  130.00
      }
    ],
    "meta": {                               // atributos personalizados del cliente
      "plan": "business",
      "ref":  "ERP-00821"
    }
  }
}
✅
Usa invoiceId como idempotency key. Es posible que recibas el mismo evento más de una vez si tu servidor tardó en responder. Almacena el invoiceId + event para evitar procesar duplicados.

Headers de la petición

Header
Valor
Content-Type
application/json; charset=utf-8
X-Event
Nombre del evento: invoice.paid, invoice.created, etc.
X-Signature
sha256=<HMAC-SHA256 en base64> — firma del cuerpo completo.
User-Agent
InvoiceSystem-Webhook/1.0

Verificación de firma

Cada petición incluye el header X-Signature con una firma HMAC-SHA256 del cuerpo JSON usando el webhook secret de tu servicio (visible en Servicios → Editar → Webhook).

⚠️
Verifica siempre la firma antes de procesar el evento. Ignorar este paso expone tu sistema a peticiones falsas de terceros.

Ejemplo en PHP

PHP
$secret    = 'tu_webhook_secret_aqui';
$body      = file_get_contents('php://input');
$header    = $_SERVER['HTTP_X_SIGNATURE'] ?? '';

// El header tiene el formato "sha256=<base64>"
$expected  = 'sha256=' . base64_encode(hash_hmac('sha256', $body, $secret, true));

if (!hash_equals($expected, $header)) {
    http_response_code(401);
    exit('Firma inválida');
}

$event = json_decode($body, true);

// Procesa el evento
match ($event['event']) {
    'invoice.paid'      => markAsPaid($event['invoice']),
    'invoice.overdue'   => sendOverdueAlert($event['invoice']),
    default             => null,
};

http_response_code(200);
echo 'ok';

Ejemplo en Node.js

Node.js
import crypto from 'crypto';
import express from 'express';

const app = express();
app.use(express.raw({ type: 'application/json' }));

app.post('/webhooks/invoices', (req, res) => {
    const secret    = process.env.WEBHOOK_SECRET;
    const signature = req.headers['x-signature'] ?? '';
    const expected  = 'sha256=' + crypto
        .createHmac('sha256', secret)
        .update(req.body)
        .digest('base64');

    if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
        return res.status(401).send('Firma inválida');
    }

    const { event, invoice } = JSON.parse(req.body);

    if (event === 'invoice.paid') {
        // Marca la orden como pagada en tu sistema
        console.log('Factura pagada:', invoice.invoiceId);
    }

    res.sendStatus(200);
});

Ejemplo en Python

Python
import hmac, hashlib, base64
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = b'tu_webhook_secret_aqui'

@app.route('/webhooks/invoices', methods=['POST'])
def webhook():
    body      = request.get_data()
    signature = request.headers.get('X-Signature', '')
    digest    = base64.b64encode(hmac.new(SECRET, body, hashlib.sha256).digest()).decode()
    expected  = f'sha256={digest}'

    if not hmac.compare_digest(signature, expected):
        abort(401, 'Firma inválida')

    data  = request.get_json(force=True)
    event = data['event']

    if event == 'invoice.paid':
        invoice = data['invoice']
        print(f"Pagada: {invoice['invoiceId']}")

    return 'ok', 200

Reintentos y timeouts

Si tu endpoint no responde con un código 2xx dentro del tiempo límite, el sistema reintenta automáticamente con backoff exponencial.

Intento
Espera
Tiempo total desde el evento
1 (inicial)
—
Inmediato
2
1 min
~1 minuto
3
5 min
~6 minutos
4
15 min
~21 minutos
5
1 hora
~1.5 horas
6 (último)
4 horas
~5.5 horas
ℹ️
Timeout por intento: 30 segundos. Procesa el evento de forma asíncrona (queue) y responde 200 ok inmediatamente para evitar timeouts en operaciones largas.

Ejemplos de uso

Activar un servicio en tu plataforma al pagar

PHP
if ($event['event'] === 'invoice.paid') {
    $invoice = $event['invoice'];
    $userId  = $invoice['meta']['userId'] ?? null;  // campo meta del cliente

    if ($userId) {
        // Activa el plan en tu base de datos
        User::find($userId)?->activate();
    }
}

Enviar alerta a Slack cuando vence una factura

PHP
if ($event['event'] === 'invoice.overdue') {
    $inv = $event['invoice'];
    Http::post(env('SLACK_WEBHOOK'), [
        'text' => "⚠️ Factura vencida: *{$inv['invoiceId']}* — {$inv['customerName']} — \${$inv['amount']} {$inv['currency']}"
    ]);
}
Aguisoftdevs Invoices · Documentación de webhooks
← Configurar productos