Iniciar sesión
Usar la API requiere aceptar los Términos de uso (versión 2026-10). Sin aceptarlos, toda llamada responde 403 terminos_pendientes.Leer los términos →

API · Webhooks

Te avisamos cuando algo cambia

En vez de consultar la API cada minuto para ver si hay algo nuevo, registra una URL de tu sistema y Wizerp le manda un aviso firmado en cuanto se crea una venta, se timbra una factura, se aplica un pago, cambia una existencia o se mueve una oportunidad. El aviso trae lo mínimo —qué pasó y la clave del recurso— y tú consultas el detalle con la API.

Firma HMAC-SHA2567 intentos en ~21 hNo consumen tu cupo mensualHasta 10 por empresa

Cómo funciona

  1. Registras una URL https de tu sistema y eliges los eventos. Te damos un secreto de firma (whsec_…) una sola vez.
  2. Cuando ocurre uno de esos eventos en tu empresa, Wizerp manda un POST con un JSON pequeño a tu URL, firmado con ese secreto.
  3. Tu sistema verifica la firma, contesta 2xx en menos de 8 segundos y, si necesita el detalle, lo consulta con la liga que trae el aviso.
  4. Si tu servidor no contesta 2xx, reintentamos con espera creciente durante unas 21 horas.

Los avisos salen en uno o dos minutos: se reparten cada minuto, y los agrupados (existencias y «actualizado») esperan además a que pase un minuto sin cambios del mismo recurso. No garantizamos el orden entre eventos: si te importa el estado final, consulta el recurso.

Crear un webhook

Desde Wizerp, en Configuración › Desarrolladores › Llaves de API y webhooks (con «Mandar prueba» y el registro de entregas), o por la API con una llave que tenga el permiso webhooks:write:

Petición · cURL
curl -X POST https://api.wizerp.com/api/v1/webhooks \
  -H "Authorization: Bearer wzk_live_…" \
  -H "X-Wizerp-Key-Id: 208_a3f91c7d" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 9b2f6c1e-5a7d-4e3b-8c0f-2d1a6e9b7c40" \
  -d '{"url":"https://tu-sistema.com/wizerp/webhooks","eventos":["venta.creada","factura.timbrada","inventario.existencia_cambiada"],"descripcion":"Mi tienda"}'

La respuesta trae el webhook y su secreto. Guárdalo: no se vuelve a mostrar (repetir la misma Idempotency-Key lo devuelve en null). Por la API, cada evento exige además el permiso de lectura de su recurso: venta.creada pide ventas:read, factura.timbrada pide facturas:read, etc. — un webhook no es un atajo para enterarte de lo que tu llave no puede consultar.

Operaciones: GET /webhooks · POST /webhooks · PUT /webhooks/{clave} (editar, pausar con "activo": false o reactivar) · DELETE /webhooks/{clave} · POST /webhooks/{clave}/secreto · POST /webhooks/{clave}/probar · GET /webhooks/{clave}/entregas · POST /webhooks/{clave}/entregas/{entrega}/reenviar.

El aviso

Un POST con Content-Type: application/json y estas cabeceras:

CabeceraQué es
X-Wizerp-Firmat=<unix>,v1=<hex>. HMAC-SHA256 de <t>.<cuerpo crudo> con tu secreto. Durante una rotación trae dos v1.
X-Wizerp-EventoEl tipo, p. ej. venta.creada.
X-Wizerp-Evento-IdEl id del evento (evt_…). El MISMO en todos los reintentos y en un reenvío: úsalo para no procesar dos veces.
X-Wizerp-EntregaLa clave de esta entrega (la de GET /webhooks/{clave}/entregas).
X-Wizerp-Intento1 en el primer intento, 2 en el primer reintento…
User-AgentWizerp-Webhooks/1.0

El cuerpo:

Cuerpo del aviso · JSON
{
    "id": "evt_3f9c1a7be0d24c55a91e6b02",
    "tipo": "venta.creada",
    "creado": "2026-10-11T18:22:04Z",
    "empresa": 208,
    "datos": {
        "venta": 48213,
        "tipo": "pedido"
    },
    "recurso": {
        "tipo": "ventas",
        "clave": 48213,
        "url": "https://api.wizerp.com/api/v1/ventas?clave=48213&tipo=pedido"
    }
}

empresa es la clave de tu empresa en Wizerp (la misma que abre tu key id): sirve si un mismo servidor recibe avisos de varias empresas. datos trae claves y poco más — nunca nombres, correos ni importes de terceros —; el detalle está en recurso.url, que consultas con tu llave.

Verificar la firma

Siempre verifica antes de confiar en un aviso: cualquiera puede mandar un POST a tu URL. Los tres pasos son iguales en cualquier lenguaje:

  1. Toma el cuerpo crudo, tal cual llegó (no el JSON ya interpretado y vuelto a escribir).
  2. Calcula HMAC-SHA256(secreto, t + "." + cuerpo) en hexadecimal y compáralo, en tiempo constante, con cada v1 de la cabecera. Basta con que uno cuadre.
  3. Rechaza avisos con t de hace más de 5 minutos (anti-replay).
Lenguaje
Tu endpoint · PHP
<?php
// Tu endpoint de webhooks. Usa el cuerpo CRUDO: re-serializar el JSON cambia los bytes y la firma ya no cuadra.
$secreto = getenv('WIZERP_WEBHOOK_SECRET');            // whsec_… (el que te dimos al crear el webhook)
$cuerpo  = file_get_contents('php://input');
$cabecera = isset($_SERVER['HTTP_X_WIZERP_FIRMA']) ? $_SERVER['HTTP_X_WIZERP_FIRMA'] : '';

$t = null; $firmas = array();
foreach (explode(',', $cabecera) as $parte) {
    $kv = explode('=', $parte, 2);
    if (count($kv) !== 2) { continue; }
    if ($kv[0] === 't')  { $t = $kv[1]; }
    if ($kv[0] === 'v1') { $firmas[] = $kv[1]; }
}
$esperada = hash_hmac('sha256', $t . '.' . $cuerpo, $secreto);
$valida = false;
foreach ($firmas as $f) { if (hash_equals($esperada, $f)) { $valida = true; } }

// Anti-replay: rechaza avisos con más de 5 minutos.
if ($t === null || !ctype_digit($t) || abs(time() - (int) $t) > 300 || !$valida) {
    http_response_code(400);
    exit;
}

$evento = json_decode($cuerpo, true);
// Idempotencia: si ya procesaste $evento['id'], contesta 200 y no lo repitas.
// Encola el trabajo y contesta YA: tienes 8 segundos.
http_response_code(200);

Para probar tu código

Con el secreto whsec_5d0c1b8e2f4a49d7b3e6a1c0f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0, t = 1791742924 y este cuerpo exacto (una línea):

Cuerpo exacto (una línea)
{"id":"evt_3f9c1a7be0d24c55a91e6b02","tipo":"venta.creada","creado":"2026-10-11T18:22:04Z","empresa":208,"datos":{"venta":48213,"tipo":"pedido"},"recurso":{"tipo":"ventas","clave":48213,"url":"https://api.wizerp.com/api/v1/ventas?clave=48213&tipo=pedido"}}

la cabecera es:

Cabecera esperada
X-Wizerp-Firma: t=1791742924,v1=d0d3913769dc99f5c8c60476591ada9e8a6180af04b8598e3ae59ad5fd928dc7

(Para esta prueba ignora el anti-replay: ese t es fijo.) En Wizerp, «Mandar prueba» manda un evento webhook.prueba real a tu URL.

Responder y reintentos

  • Contesta 2xx en menos de 8 segundos. Haz el trabajo pesado después (una cola): si tardas, cuenta como fallo y lo reintentamos.
  • No seguimos redirecciones: un 301/302 es un fallo. Usa la URL final.
  • Cualquier otro código, un timeout o un error de red se reintenta: a 1 minuto, 5 minutos, 30 minutos, 2 horas, 6 horas y 12 horas (7 intentos en unas 21 horas, con un poco de variación). Después la entrega queda como fallida; puedes reenviarla.
  • Puede llegarte el mismo evento más de una vez (un reintento después de un timeout tuyo, o un reenvío): deduplica por X-Wizerp-Evento-Id.
  • Desactivación automática: si un webhook acumula 15 entregas fallidas seguidas durante más de 24 horas, lo desactivamos, descartamos sus pendientes y avisamos por correo a quien lo creó. Lo reactivas en Wizerp o con PUT /webhooks/{clave} y {"activo": true}. Lo que pasó mientras estuvo desactivado sigue disponible en la API.

Seguridad

  • Sólo https, al puerto 443, con nombre de dominio (no una IP). El certificado debe ser válido.
  • No mandamos avisos a direcciones privadas, locales ni reservadas: el dominio se resuelve en cada entrega y, si apunta a una red interna, la entrega falla con destino_bloqueado.
  • El secreto se guarda cifrado y sólo se muestra al crearlo o rotarlo. Si sospechas que se filtró, rótalo: el inmediato anterior sigue firmando 24 horas (dos v1 en la cabecera) para que lo cambies sin perder avisos. Si rotas dos veces en ese lapso, el más viejo deja de firmar.
  • Cada webhook es de una empresa y sólo recibe eventos de esa empresa.

Límites y cupo

  • Hasta 10 webhooks por empresa. Un webhook puede recibir varios eventos: agrégalos al mismo en vez de crear otro.
  • Las entregas no cuentan contra el cupo mensual de la API. Es el punto: un aviso te evita cientos de consultas. Las llamadas que haces para administrar webhooks o para leer el detalle de un aviso sí cuentan, como cualquier petición.
  • Los eventos y el registro de entregas se guardan 30 días.

Catálogo de eventos

Los eventos de existencia y de actualizado llegan agrupados: todos los cambios del mismo recurso en un minuto son un solo aviso. Por eso no traen la cantidad nueva — consulta el recurso y verás el valor vigente.
venta.creadaVenta creada

Se creó un documento de venta: cotización, pedido, remisión (venta), factura o anticipo, desde cualquier canal (pantalla, punto de venta, marketplace, tienda en línea o la API). Convertir una cotización en pedido o un pedido en factura NO es una venta nueva: no emite este evento (la factura emite factura.timbrada).

Permiso para suscribirse por la API: ventas:readFrecuencia: Una vez por recursoDetalle: GET /ventas
Payload de ejemplo
Payload · JSON
{
    "id": "evt_3f9c1a7be0d24c55a91e6b02",
    "tipo": "venta.creada",
    "creado": "2026-10-11T18:22:04Z",
    "empresa": 208,
    "datos": {
        "venta": 48213,
        "tipo": "pedido"
    },
    "recurso": {
        "tipo": "ventas",
        "clave": 48213,
        "url": "https://api.wizerp.com/api/v1/ventas?clave=48213&tipo=pedido"
    }
}
factura.timbradaFactura timbrada

El SAT (a través del PAC) timbró una factura de venta: ya tiene folio fiscal (UUID). Sólo facturas de ingreso; las notas de crédito y los complementos de pago no emiten este evento.

Permiso para suscribirse por la API: facturas:readFrecuencia: Una vez por recursoDetalle: GET /facturas
Payload de ejemplo
Payload · JSON
{
    "id": "evt_3f9c1a7be0d24c55a91e6b02",
    "tipo": "factura.timbrada",
    "creado": "2026-10-11T18:22:04Z",
    "empresa": 208,
    "datos": {
        "factura": 99120,
        "venta": 48213,
        "uuid": "6F1C2B9E-3D4A-4F7B-9A21-0C5E8D7B1A23"
    },
    "recurso": {
        "tipo": "facturas",
        "clave": 99120,
        "url": "https://api.wizerp.com/api/v1/facturas?clave=99120"
    }
}
factura.canceladaFactura cancelada

La cancelación de una factura quedó firme: el PAC la aceptó en facturas que no requieren aceptación del receptor, o el SAT confirmó la cancelación en las que sí la requieren. Una solicitud rechazada por el receptor no emite este evento.

Permiso para suscribirse por la API: facturas:readFrecuencia: Una vez por recursoDetalle: GET /facturas
Payload de ejemplo
Payload · JSON
{
    "id": "evt_3f9c1a7be0d24c55a91e6b02",
    "tipo": "factura.cancelada",
    "creado": "2026-10-11T18:22:04Z",
    "empresa": 208,
    "datos": {
        "factura": 99120,
        "venta": 48213,
        "uuid": "6F1C2B9E-3D4A-4F7B-9A21-0C5E8D7B1A23"
    },
    "recurso": {
        "tipo": "facturas",
        "clave": 99120,
        "url": "https://api.wizerp.com/api/v1/facturas?clave=99120"
    }
}
cxc.pago_aplicadoPago aplicado a una cuenta por cobrar

Se aplicó un pago a un documento de tu cartera: un depósito identificado (pantalla de Bancos o Cobranza, conciliación, pasarela de pago o la API), un saldo a favor o un anticipo. Un depósito que paga varias cuentas emite un evento por cuenta.

Permiso para suscribirse por la API: cxc:readFrecuencia: Una vez por recursoDetalle: GET /cxc-pagos
Payload de ejemplo
Payload · JSON
{
    "id": "evt_3f9c1a7be0d24c55a91e6b02",
    "tipo": "cxc.pago_aplicado",
    "creado": "2026-10-11T18:22:04Z",
    "empresa": 208,
    "datos": {
        "pago": 77031,
        "cxc": 51220,
        "importe": "1160.00"
    },
    "recurso": {
        "tipo": "cxc-pagos",
        "clave": 77031,
        "url": "https://api.wizerp.com/api/v1/cxc-pagos?clave=77031"
    }
}
cliente.creadoCliente creado

Se dio de alta un cliente en tu cartera (pantalla, importación de pedidos de marketplace o la API). Los prospectos del CRM no emiten este evento.

Permiso para suscribirse por la API: clientes:readFrecuencia: Una vez por recursoDetalle: GET /clientes
Payload de ejemplo
Payload · JSON
{
    "id": "evt_3f9c1a7be0d24c55a91e6b02",
    "tipo": "cliente.creado",
    "creado": "2026-10-11T18:22:04Z",
    "empresa": 208,
    "datos": {
        "cliente": 10342
    },
    "recurso": {
        "tipo": "clientes",
        "clave": 10342,
        "url": "https://api.wizerp.com/api/v1/clientes?clave=10342"
    }
}
cliente.actualizadoCliente actualizado

Se editaron los datos de un cliente. Los cambios del mismo cliente dentro de un minuto llegan como un solo evento: consulta el cliente para ver cómo quedó.

Permiso para suscribirse por la API: clientes:readFrecuencia: Agrupado (1 por minuto por recurso)Detalle: GET /clientes
Payload de ejemplo
Payload · JSON
{
    "id": "evt_3f9c1a7be0d24c55a91e6b02",
    "tipo": "cliente.actualizado",
    "creado": "2026-10-11T18:22:04Z",
    "empresa": 208,
    "datos": {
        "cliente": 10342
    },
    "recurso": {
        "tipo": "clientes",
        "clave": 10342,
        "url": "https://api.wizerp.com/api/v1/clientes?clave=10342"
    }
}
producto.creadoProducto creado

Se dio de alta un producto en tu catálogo (pantalla, sincronización de marketplace o la API).

Permiso para suscribirse por la API: productos:readFrecuencia: Una vez por recursoDetalle: GET /productos
Payload de ejemplo
Payload · JSON
{
    "id": "evt_3f9c1a7be0d24c55a91e6b02",
    "tipo": "producto.creado",
    "creado": "2026-10-11T18:22:04Z",
    "empresa": 208,
    "datos": {
        "producto": 20581,
        "sku": "CAM-ROJA-M"
    },
    "recurso": {
        "tipo": "productos",
        "clave": 20581,
        "url": "https://api.wizerp.com/api/v1/productos?clave=20581"
    }
}
producto.actualizadoProducto actualizado

Se editaron los datos de un producto. Los cambios del mismo producto dentro de un minuto llegan como un solo evento. Los precios y las existencias tienen sus propias consultas (GET /precios, GET /inventario).

Permiso para suscribirse por la API: productos:readFrecuencia: Agrupado (1 por minuto por recurso)Detalle: GET /productos
Payload de ejemplo
Payload · JSON
{
    "id": "evt_3f9c1a7be0d24c55a91e6b02",
    "tipo": "producto.actualizado",
    "creado": "2026-10-11T18:22:04Z",
    "empresa": 208,
    "datos": {
        "producto": 20581,
        "sku": "CAM-ROJA-M"
    },
    "recurso": {
        "tipo": "productos",
        "clave": 20581,
        "url": "https://api.wizerp.com/api/v1/productos?clave=20581"
    }
}
inventario.existencia_cambiadaExistencia cambiada

Cambió la existencia o lo apartado de un producto en un almacén (venta, compra, traspaso, ajuste, pedido). AGRUPADO: todos los cambios del mismo renglón de existencia dentro de un minuto llegan como UN evento, así que no recibirás uno por pieza vendida. El evento no trae la cantidad a propósito: consulta GET /inventario con la clave y obtendrás la existencia vigente, no una que ya cambió.

Permiso para suscribirse por la API: inventario:readFrecuencia: Agrupado (1 por minuto por recurso)Detalle: GET /inventario
Payload de ejemplo
Payload · JSON
{
    "id": "evt_3f9c1a7be0d24c55a91e6b02",
    "tipo": "inventario.existencia_cambiada",
    "creado": "2026-10-11T18:22:04Z",
    "empresa": 208,
    "datos": {
        "existencia": 335871,
        "producto": 20581,
        "almacen": "MTZ"
    },
    "recurso": {
        "tipo": "inventario",
        "clave": 335871,
        "url": "https://api.wizerp.com/api/v1/inventario?clave=335871"
    }
}
oportunidad.creadaOportunidad creada

Se creó una oportunidad en el embudo del CRM (pantalla o la API).

Permiso para suscribirse por la API: oportunidades:readFrecuencia: Una vez por recursoDetalle: GET /oportunidades
Payload de ejemplo
Payload · JSON
{
    "id": "evt_3f9c1a7be0d24c55a91e6b02",
    "tipo": "oportunidad.creada",
    "creado": "2026-10-11T18:22:04Z",
    "empresa": 208,
    "datos": {
        "oportunidad": 811,
        "etapa": 3
    },
    "recurso": {
        "tipo": "oportunidades",
        "clave": 811,
        "url": "https://api.wizerp.com/api/v1/oportunidades?clave=811"
    }
}
oportunidad.etapa_cambiadaOportunidad cambió de etapa

Una oportunidad se movió de etapa del embudo, incluidas ganarla y perderla. Trae la etapa anterior y la nueva (claves de GET /etapas-oportunidad) y el estatus resultante. Reordenar tarjetas dentro de la misma etapa no emite este evento.

Permiso para suscribirse por la API: oportunidades:readFrecuencia: Cada cambioDetalle: GET /oportunidades
Payload de ejemplo
Payload · JSON
{
    "id": "evt_3f9c1a7be0d24c55a91e6b02",
    "tipo": "oportunidad.etapa_cambiada",
    "creado": "2026-10-11T18:22:04Z",
    "empresa": 208,
    "datos": {
        "oportunidad": 811,
        "etapa_anterior": 3,
        "etapa": 5,
        "estatus": "ganada"
    },
    "recurso": {
        "tipo": "oportunidades",
        "clave": 811,
        "url": "https://api.wizerp.com/api/v1/oportunidades?clave=811"
    }
}
© 2026 Wizerp · developer.wizerp.com · Referencia de la API · MCP · Términos de uso