Saltar al contenido
WbizTool

API v2 (beta)

Webhooks

Un endpoint de webhook es una URL de tu servidor a la que WbizTool llama cuando le pasa algo a un mensaje: se envió, falló, se canceló o caducó. Te enteras al momento en lugar de consultar una y otra vez, y cada solicitud va firmada para que puedas comprobar que viene de WbizTool.

OperaciónEndpointPermiso
Añadir un endpointPOST /api/v2/webhooksmessages:read
Listar endpointsGET /api/v2/webhooksmessages:read
Obtener un endpointGET /api/v2/webhooks/{id}messages:read
Quitar un endpointDELETE /api/v2/webhooks/{id}messages:read
Cambiar el secreto de firmaPOST /api/v2/webhooks/{id}/rotate-secretmessages:read
Enviar un evento de pruebaPOST /api/v2/webhooks/{id}/testmessages:read

Los endpoints pertenecen al espacio de trabajo y reciben eventos de todos sus mensajes, los haya enviado quien los haya enviado: la API, el panel, las campañas, los recordatorios o las integraciones. Por eso una clave limitada a algunos números no puede gestionarlos: todas las llamadas responden 403 number_not_allowed.

Eventos#

TipoCuándo
message.sentUn mensaje salió de tu número.
message.failedUn mensaje no se pudo enviar; error.message dice por qué.
message.cancelledSe canceló un mensaje en espera: con la API, desde el panel o porque se quitó su número o su campaña.
message.expiredUn mensaje no se envió antes de su expires_at.
pingUn evento de prueba que pediste.

Los eventos de números (conectado, sesión cerrada) y de mensajes entrantes llegarán más adelante. Los tics de entregado y leído no envían eventos; obtén el mensaje para verlos.

Añadir un endpoint#

POSThttps://wbiztool.com/api/v2/webhooks

Cuerpo: JSON (application/json)

Cuerpo

urlstringobligatorio

Tu endpoint: una dirección pública https://, de hasta 500 caracteres. Se rechazan las direcciones http://, privadas y locales (como localhost o 192.168.x.x).

eventsarray of stringsopcional

Los tipos de evento que recibe: cualquiera de message.sent, message.failed, message.cancelled y message.expired. Predeterminado: los cuatro. ping siempre llega cuando lo pides.

descriptionstringopcional

Una nota para ti, de hasta 100 caracteres, como Order system.

Un espacio de trabajo puede tener hasta 5 endpoints. La respuesta es 201 con el endpoint y su secret de firma, que solo se muestra esta vez. Guárdalo donde tu receptor pueda leerlo, como una variable de entorno. Las respuestas posteriores solo muestran secret_last4; si pierdes el secreto, cámbialo.

curl -X POST https://wbiztool.com/api/v2/webhooks \
  -H "Authorization: Bearer $WBIZTOOL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/wbiztool/events",
    "events": ["message.sent", "message.failed"],
    "description": "Order system"
  }'

Respuesta (201):

{
  "id": 41,
  "url": "https://example.com/wbiztool/events",
  "description": "Order system",
  "events": ["message.sent", "message.failed"],
  "secret_last4": "9c1e",
  "active": true,
  "disabled_reason": null,
  "created_at": "2026-10-18T11:20:05.733Z",
  "last_delivery": null,
  "secret": "3f0b…9c1e"
}

El secret real tiene 64 caracteres hexadecimales. Un reintento de esta solicitud con la misma Idempotency-Key no añade un segundo endpoint, y su respuesta no incluye el secreto.

Errores al añadir un endpoint#

HTTPCódigoCuándo
400validation_failedFalta url o es demasiado larga, events está vacío o tiene un tipo desconocido, o un campo es desconocido
403webhook_limit_reachedEl espacio de trabajo ya tiene 5 endpoints
403number_not_allowedLa clave está limitada a algunos números
422invalid_webhook_urlurl no es una dirección pública https://

Listar endpoints#

GEThttps://wbiztool.com/api/v2/webhooks

Los endpoints del espacio de trabajo que reciben eventos, del más antiguo al más reciente, en una sola lista data.

curl https://wbiztool.com/api/v2/webhooks \
  -H "Authorization: Bearer $WBIZTOOL_API_KEY"

La lista no incluye los endpoints que se detuvieron. Los Propietarios también ven los endpoints, y pueden quitarlos, en Configuración → Claves API, en Endpoints de webhooks.

Obtener un endpoint#

GEThttps://wbiztool.com/api/v2/webhooks/{id}

Un endpoint. last_delivery muestra cómo fue su última entrega. Un endpoint que no existe o que se quitó responde 404 webhook_not_found; uno detenido se muestra con active: false.

Quitar un endpoint#

DELETEhttps://wbiztool.com/api/v2/webhooks/{id}

No se le envía nada más, tampoco los reintentos que siguen en espera. La respuesta es {"id": 41, "deleted": true}.

Cambiar el secreto de firma#

POSThttps://wbiztool.com/api/v2/webhooks/{id}/rotate-secret

Crea un secreto nuevo para el endpoint. La respuesta es 200 con el endpoint y su nuevo secret, que solo se muestra esta vez. El secreto anterior deja de funcionar al instante: los eventos enviados a partir de ese momento se firman con el nuevo, incluidos los reintentos de eventos anteriores. Actualiza tu receptor de inmediato.

curl -X POST https://wbiztool.com/api/v2/webhooks/41/rotate-secret \
  -H "Authorization: Bearer $WBIZTOOL_API_KEY"

Enviar un evento de prueba#

POSThttps://wbiztool.com/api/v2/webhooks/{id}/test

Pone en cola un evento ping para este endpoint, firmado como todos los eventos, para que puedas probar tu receptor. La respuesta es 202:

{ "event_id": "evt_8a2f4c1d9b7e6a5f4c3b2a19", "type": "ping", "queued": true }

Tu endpoint recibe:

{
  "id": "evt_8a2f4c1d9b7e6a5f4c3b2a19",
  "type": "ping",
  "created_at": "2026-10-18T11:21:40.002Z",
  "data": { "webhook_id": 41 }
}

El contenido del evento#

Cada evento es un POST con un cuerpo JSON:

{
  "id": "evt_c41d7e2a9f3b8c6d5e4f3a21",
  "type": "message.sent",
  "created_at": "2026-10-18T10:15:09.912Z",
  "data": {
    "message": {
      "id": 9817263,
      "status": "sent",
      "from": { "id": 101, "phone": "919800000000", "platform": "whatsapp" },
      "to": { "phone": "+919876543210", "contact_id": 991 },
      "text": "Hi Asha, your order #1042 is ready.",
      "media": null,
      "send_at": null,
      "expires_at": null,
      "created_at": "2026-10-18T10:15:02.311Z",
      "sent_at": "2026-10-18T10:15:09.870Z",
      "error": null
    }
  }
}
CampoDescripción
idEl id del evento: evt_ y 24 caracteres. El mismo evento conserva su id en cada reintento.
typeUno de los tipos de evento.
created_atCuándo ocurrió el evento.
data.messageEn los eventos message.*: el Message tal como era en ese momento. Un media.url que contenga funciona al menos 26 horas.
data.webhook_idEn ping: el id del endpoint.

Cabeceras:

CabeceraValor
Content-Typeapplication/json
User-AgentWbizTool-Webhooks/2
WbizTool-Event-IdEl id del evento, el mismo en cada reintento
WbizTool-TimestampCuándo se envió este intento, en segundos Unix, como 1792310409
WbizTool-Signaturev1= y el HMAC-SHA256 en hexadecimal de <timestamp>.<raw body>, hecho con el secreto del endpoint

Comprobar la firma#

Comprueba cada solicitud antes de fiarte de ella: calcula la firma a partir del cuerpo sin procesar (antes de que tu framework analice el JSON), compárala con la cabecera en tiempo constante y rechaza las marcas de tiempo con más de 5 minutos de diferencia, para que no se pueda repetir una solicitud antigua. Es el mismo esquema que las firmas de webhooks de la API v1, con el secreto propio del endpoint.

Después usa WbizTool-Event-Id para descartar los repetidos: el mismo evento puede llegar más de una vez, por ejemplo cuando tu respuesta se perdió y volvimos a intentarlo.

import hashlib, hmac, json, os, time

SECRET = os.environ["WBIZTOOL_WEBHOOK_SECRET"]

def verify(secret: str, body: bytes, headers) -> bool:
    ts, sig = headers.get("WbizTool-Timestamp", ""), headers.get("WbizTool-Signature", "")
    if not ts.isdigit() or abs(time.time() - int(ts)) > 300:
        return False
    mac = "v1=" + hmac.new(secret.encode(), ts.encode() + b"." + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(mac, sig)

# Flask
from flask import Flask, request

app = Flask(__name__)
seen = set()  # use your database in production

@app.post("/wbiztool/events")
def events():
    body = request.get_data()  # the raw bytes
    if not verify(SECRET, body, request.headers):
        return "", 401
    event_id = request.headers["WbizTool-Event-Id"]
    if event_id in seen:
        return "", 200  # a repeat: already handled
    seen.add(event_id)
    event = json.loads(body)
    if event["type"] == "message.failed":
        message = event["data"]["message"]
        print("Failed:", message["id"], message["error"]["message"])
    return "", 200

En Django, pasa request.body y request.headers.

Responde 2xx rápido y haz el trabajo lento después (en una cola, por ejemplo).

Reintentos#

  • Responde con cualquier 2xx en 10 segundos. Todo lo demás es un fallo: otro código de estado (incluidos los 4xx), ninguna respuesta en 10 segundos o ninguna conexión en 3. Se siguen las redirecciones, 3 como máximo, y cada una debe llevar a una dirección pública.
  • Una entrega fallida se vuelve a intentar pasados 1, 5 y 15 minutos, y luego 1, 2, 4, 8 y 8 horas: 9 intentos en unas 23 horas. Tras el último, ese evento se descarta.
  • El reintento tiene el mismo cuerpo y el mismo WbizTool-Event-Id, con un WbizTool-Timestamp y una firma nuevos.
  • El orden no está garantizado. Un message.sent reintentado puede llegar después de un evento posterior. Usa el status del mensaje, u obtén el mensaje, cuando el orden importe.

last_delivery en el endpoint muestra cómo fue la última entrega: state (pending, delivered o failed), el status_code con el que respondió tu servidor y cuándo se creó el evento.

Cuándo se detienen los endpoints#

Un endpoint deja de recibir eventos cuando ocurre una de estas cosas. Sigue apareciendo en GET /webhooks con active: false y un disabled_reason, para que veas por qué; todavía se puede leer y quitar, pero no probar ni darle un secreto nuevo (404).

  • Lleva 24 horas fallando. Si no se le ha entregado nada en 24 horas mientras sus entregas seguían fallando, se desactiva y se descartan sus reintentos en espera. Configuración → Claves API lo muestra entonces como Desactivado. Arregla tu servidor y vuelve a añadir el endpoint.
  • La clave que lo creó se revoca o caduca. Revocar una clave detiene todo lo que configuró. Vuelve a añadir el endpoint con otra clave.
  • Se quita, con Quitar un endpoint o en Configuración → Claves API.

Los endpoints detenidos no cuentan para los 5.

El objeto webhook endpoint#

CampoTipoDescripción
idintegerEl id del endpoint.
urlstringAdonde se envían los eventos.
descriptionstringTu nota; "" si no hay ninguna.
eventsarrayLos tipos de evento que recibe.
secretstringEl secreto de firma: solo en las respuestas de Añadir un endpoint y Cambiar el secreto de firma.
secret_last4stringLos 4 últimos caracteres del secreto, para saber cuál tienes.
activebooleanfalse cuando ya no recibe eventos; consulta Cuándo se detienen los endpoints.
disabled_reasonstring or nullPor qué se detuvo (24 horas fallando, o la clave que lo creó se revocó o caducó); null mientras está activo.
created_atstringCuándo se añadió.
last_deliveryobject or nullLa última entrega intentada: state, status_code (o null si tu servidor no respondió) y created_at. null antes de la primera.

Relacionado#