Webhooks

Los webhooks te permiten recibir notificaciones en tiempo real cuando ocurren eventos importantes en tu cuenta, como cuando una factura es aceptada o rechazada por la AEAT.

CF
Certifactu
TU
Tu servidor

Cuando el estado de una factura cambia, Certifactu envía una petición HTTP POST a tu endpoint configurado.

Crear Webhook

Configura un endpoint en tu servidor para recibir notificaciones. Puedes suscribirte a eventos específicos o recibir todos.

POST /invoices/webhooks

Registra un nuevo webhook

Parámetros

url string requerido

URL de tu servidor que recibirá las notificaciones. Debe ser HTTPS.

secret string opcional

Secreto para firmar los payloads y verificar autenticidad. Muy recomendado.

events array opcional

Lista de eventos a los que suscribirse. Si se omite, recibirás todos.

Por defecto: ["status_changed"]

Crear webhook
curl -X POST https://api.certifactu.com/api/v1/invoices/webhooks \
  -H "Content-Type: application/json" \
  -H "X-API-Key: sk_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -d '{
    "url": "https://mi-servidor.com/webhooks/certifactu",
    "secret": "mi_secreto_super_seguro",
    "events": ["status_changed", "rejected"]
  }'

Eventos Disponibles

Puedes suscribirte a los siguientes eventos:

status_changed

Se dispara cuando cambia el estado de cualquier factura.

accepted

Cuando una factura es aceptada por la AEAT.

rejected

Cuando una factura es rechazada (requiere subsanación).

cancelled

Cuando una factura es anulada por el usuario.

Formato del Payload

Tu servidor recibirá una petición POST con el siguiente formato JSON:

Ejemplo de payload
{
  "event": "status_changed",
  "timestamp": "2024-01-30T12:00:00Z",
  "webhook_id": "wh_abc123def456",
  "data": {
    "invoice_id": "550e8400-e29b-41d4-a716-446655440000",
    "series": "F2024",
    "number": "001",
    "old_status": "QUEUED",
    "new_status": "ACCEPTED",
    "aeat_csv": "CSV123456789ABCDEF"
  }
}

Estados de factura

QUEUED Pendiente de envío
SENDING Enviando a AEAT
ACCEPTED Aceptada por AEAT
o
REJECTED Rechazada por AEAT

Verificación de Firma

Si configuraste un secret, incluiremos una cabecera con la firma HMAC SHA-256 del cuerpo de la petición. Siempre verifica esta firma antes de procesar el webhook.

X-Webhook-Signature: sha256=f4b7c8d9e0a1b2c3d4e5f6...

Cómo verificar la firma

Verificación de firma
import hmac
import hashlib

def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
    """Verifica la firma del webhook."""
    expected = hmac.new(
        secret.encode(),
        payload,
        hashlib.sha256
    ).hexdigest()

    # Extraer hash de la cabecera "sha256=..."
    received = signature.replace("sha256=", "")

    return hmac.compare_digest(expected, received)

# En tu endpoint Flask/FastAPI:
@app.post("/webhooks/certifactu")
async def handle_webhook(request: Request):
    payload = await request.body()
    signature = request.headers.get("X-Webhook-Signature", "")

    if not verify_webhook(payload, signature, WEBHOOK_SECRET):
        raise HTTPException(status_code=401, detail="Invalid signature")

    data = json.loads(payload)
    # Procesar el webhook...