Webhooks
El SRI tarda de segundos a minutos en autorizar. En vez de consultar, registra una URL y Chaski te avisa.
Registrar un endpoint
Sección titulada «Registrar un endpoint»En el panel (Webhooks) o por API:
curl https://api.chaski.ec/v1/webhook-endpoints \ -H "Authorization: Bearer $CHASKI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://tu-app.com/webhooks/chaski", "events": ["invoice.authorized", "invoice.rejected"] }'La respuesta trae el secreto whsec_… para verificar las firmas. Se muestra una sola vez. Cada endpoint pertenece al ambiente de la key con que lo creaste.
Editar, pausar o rotar el secreto
Sección titulada «Editar, pausar o rotar el secreto»PATCH /v1/webhook-endpoints/{id}cambiaurl,description,eventsoenabled. Conenabled: falseel endpoint deja de recibir eventos hasta que lo vuelvas a activar.POST /v1/webhook-endpoints/{id}/rotate-secretgenera un secreto nuevo y lo devuelve una sola vez. El anterior deja de servir de inmediato, así que actualízalo en tu servidor enseguida.
También puedes hacerlo desde el panel, en el detalle del webhook.
Eventos
Sección titulada «Eventos»| Evento | Cuándo |
|---|---|
invoice.authorized |
El SRI autorizó la factura. |
invoice.rejected |
El SRI la rechazó (data.sri_messages dice por qué). |
invoice.failed |
No se pudo enviar después de todos los reintentos. |
invoice.voided |
Registraste la anulación. |
credit_note.authorized / .rejected / .failed |
Lo mismo, para notas de crédito. |
debit_note.authorized / .rejected / .failed |
Lo mismo, para notas de débito. |
withholding.authorized / .rejected / .failed |
Lo mismo, para comprobantes de retención. |
delivery_guide.authorized / .rejected / .failed |
Lo mismo, para guías de remisión. |
purchase_settlement.authorized / .rejected / .failed |
Lo mismo, para liquidaciones de compra. |
document.email_bounced |
El correo al comprador rebotó. |
export.ready / export.failed |
Terminó una exportación. |
certificate.expiring |
El certificado de un emisor vence pronto. |
issuer.live_enabled |
Un emisor pasó a producción. |
usage.threshold_reached |
El consumo del mes llegó al 80% o al 100% del plan. |
El pedido
Sección titulada «El pedido»POST /webhooks/chaskiContent-Type: application/jsonChaski-Event-Id: evt_01jbx3k9q2f8m4tn6vwz0yh5ceChaski-Signature: t=1793700000,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
{ "id": "evt_01jbx3k9q2f8m4tn6vwz0yh5ce", "type": "invoice.authorized", "environment": "live", "created_at": "2026-11-03T15:04:05.000Z", "data": { "id": "inv_…", "status": "authorized", "number": "001-001-000001042", … }}Responde con cualquier 2xx en menos de 10 segundos. Si tu servidor falla o tarda, reintentamos a los 1, 5 y 30 minutos, 2, 12 y 24 horas. El mismo evento puede llegar más de una vez: usa Chaski-Event-Id para no procesarlo dos veces.
Verificar la firma
Sección titulada «Verificar la firma»Chaski-Signature es t=<unix>,v1=<firma>, donde la firma es el HMAC-SHA256 en hexadecimal de "<t>.<cuerpo crudo>" con tu secreto. Rechaza firmas de más de 5 minutos.
import { verifyWebhook } from '@chaski/sdk';
app.post('/webhooks/chaski', express.raw({ type: 'application/json' }), async (req, res) => { try { const event = await verifyWebhook(req.body.toString('utf8'), req.header('chaski-signature'), process.env.CHASKI_WEBHOOK_SECRET!); // procesa event.type / event.data res.sendStatus(200); } catch { res.sendStatus(400); }});import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(rawBody: string, header: string, secret: string): boolean { const parts = Object.fromEntries(header.split(',').map((p) => p.split('='))); const t = Number(parts.t); if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false; const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest(); const received = Buffer.from(parts.v1 ?? '', 'hex'); return expected.length === received.length && timingSafeEqual(expected, received);}function chaski_verify(string $rawBody, string $header, string $secret): bool { parse_str(str_replace(',', '&', $header), $parts); $t = (int) ($parts['t'] ?? 0); if (!$t || abs(time() - $t) > 300) return false; $expected = hash_hmac('sha256', "$t.$rawBody", $secret); return hash_equals($expected, $parts['v1'] ?? '');}Si se te pasó algo
Sección titulada «Si se te pasó algo»GET /v1/events lista los eventos de tu cuenta (filtra con type), y GET /v1/webhook-endpoints/{id}/deliveries muestra cada intento de entrega con su respuesta. Si una entrega quedó failed (por ejemplo, tu servidor estuvo caído más de lo que duran los reintentos), POST /v1/webhook-endpoints/{id}/deliveries/{deliveryId}/retry la vuelve a enviar.
