Aller au contenu
WbizTool

API v2 (bêta)

Webhooks

Un endpoint de webhook est une URL de votre serveur que WbizTool appelle lorsqu'il arrive quelque chose à un message : il a été envoyé, il a échoué, il a été annulé ou il a expiré. Vous êtes informé tout de suite au lieu d'interroger l'API en boucle, et chaque requête est signée pour que vous puissiez vérifier qu'elle vient de WbizTool.

OpérationEndpointScope
Ajouter un endpointPOST /api/v2/webhooksmessages:read
Lister les endpointsGET /api/v2/webhooksmessages:read
Obtenir un endpointGET /api/v2/webhooks/{id}messages:read
Supprimer un endpointDELETE /api/v2/webhooks/{id}messages:read
Remplacer le secret de signaturePOST /api/v2/webhooks/{id}/rotate-secretmessages:read
Envoyer un événement de testPOST /api/v2/webhooks/{id}/testmessages:read

Les endpoints appartiennent à l'espace de travail et reçoivent les événements de tous ses messages, quelle que soit leur origine : l'API, le tableau de bord, les campagnes, les rappels ou les intégrations. C'est pourquoi une clé limitée à certains numéros ne peut pas les gérer : chaque appel répond 403 number_not_allowed.

Événements#

TypeQuand
message.sentUn message est parti de votre numéro.
message.failedUn message n'a pas pu être envoyé ; error.message indique pourquoi.
message.cancelledUn message en attente a été annulé : avec l'API, depuis le tableau de bord, ou parce que son numéro ou sa campagne a été supprimé.
message.expiredUn message n'a pas été envoyé avant son expires_at.
pingUn événement de test que vous avez demandé.

Les événements pour les numéros (connecté, déconnecté) et pour les messages entrants viendront plus tard. Les coches de distribution et de lecture n'envoient pas d'événement ; obtenez le message pour les voir.

Ajouter un endpoint#

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

Corps: JSON (application/json)

Corps

urlstringobligatoire

Votre endpoint : une adresse https:// publique, jusqu'à 500 caractères. Les adresses http://, privées et locales (comme localhost ou 192.168.x.x) sont refusées.

eventsarray of stringsfacultatif

Les types d'événements qu'il reçoit : n'importe lesquels parmi message.sent, message.failed, message.cancelled et message.expired. Par défaut : les quatre. ping arrive toujours quand vous en demandez un.

descriptionstringfacultatif

Une note pour vous, jusqu'à 100 caractères, comme Order system.

Un espace de travail peut avoir jusqu'à 5 endpoints. La réponse est 201 avec l'endpoint et son secret de signature, affiché cette seule fois. Conservez-le là où votre récepteur peut le lire, par exemple dans une variable d'environnement. Les réponses suivantes n'affichent que secret_last4 ; si vous perdez le secret, remplacez-le.

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"
  }'

Réponse (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"
}

Le vrai secret fait 64 caractères hexadécimaux. Une nouvelle tentative de cette requête avec la même Idempotency-Key n'ajoute pas de deuxième endpoint, et sa réponse omet le secret.

Erreurs à l'ajout d'un endpoint#

HTTPCodeQuand
400validation_failedurl manque ou est trop long, events est vide ou contient un type inconnu, ou un champ est inconnu
403webhook_limit_reachedL'espace de travail a déjà 5 endpoints
403number_not_allowedLa clé est limitée à certains numéros
422invalid_webhook_urlurl n'est pas une adresse https:// publique

Lister les endpoints#

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

Les endpoints de l'espace de travail qui reçoivent des événements, les plus anciens d'abord, dans une seule liste data.

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

La liste omet les endpoints arrêtés. Les propriétaires voient aussi les endpoints, et peuvent les retirer, sur Paramètres → Clés API sous Endpoints de webhook.

Obtenir un endpoint#

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

Un endpoint. last_delivery montre comment s'est passée sa dernière livraison. Un endpoint qui n'existe pas ou a été supprimé répond 404 webhook_not_found ; un endpoint arrêté est affiché avec active: false.

Supprimer un endpoint#

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

Plus rien ne lui est envoyé, y compris les nouvelles tentatives encore en attente. La réponse est {"id": 41, "deleted": true}.

Remplacer le secret de signature#

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

Crée un nouveau secret pour l'endpoint. La réponse est 200 avec l'endpoint et son nouveau secret, affiché cette seule fois. L'ancien secret cesse immédiatement de fonctionner : les événements envoyés après cela sont signés avec le nouveau, y compris les nouvelles tentatives d'événements antérieurs. Mettez votre récepteur à jour tout de suite.

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

Envoyer un événement de test#

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

Met en file d'attente un événement ping vers cet endpoint, signé comme tous les événements, pour que vous puissiez tester votre récepteur. La réponse est 202 :

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

Votre endpoint reçoit :

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

Le contenu d'un événement#

Chaque événement est un POST avec un corps 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
    }
  }
}
ChampDescription
idL'identifiant de l'événement, evt_ suivi de 24 caractères. Un même événement garde son identifiant à chaque nouvelle tentative.
typeL'un des types d'événements.
created_atQuand l'événement s'est produit.
data.messagePour les événements message.* : le Message tel qu'il était à ce moment-là. Un media.url qu'il contient fonctionne pendant au moins 26 heures.
data.webhook_idPour ping : l'identifiant de l'endpoint.

En-têtes :

En-têteValeur
Content-Typeapplication/json
User-AgentWbizTool-Webhooks/2
WbizTool-Event-IdL'id de l'événement, le même à chaque nouvelle tentative
WbizTool-TimestampLe moment où cette tentative a été envoyée, en secondes Unix, comme 1792310409
WbizTool-Signaturev1= suivi du HMAC-SHA256 en hexadécimal de <timestamp>.<raw body>, calculé avec le secret de l'endpoint

Vérifier la signature#

Vérifiez chaque requête avant de lui faire confiance : calculez la signature à partir du corps brut (avant que votre framework n'analyse le JSON), comparez-la à l'en-tête en temps constant, et refusez les horodatages décalés de plus de 5 minutes, pour qu'une ancienne requête ne puisse pas être rejouée. C'est le même principe que les signatures des webhooks de l'API v1, avec le secret propre à l'endpoint.

Ensuite, utilisez WbizTool-Event-Id pour écarter les doublons : un même événement peut arriver plus d'une fois, par exemple quand votre réponse s'est perdue et que nous avons réessayé.

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

Avec Django, passez request.body et request.headers.

Répondez 2xx rapidement, puis faites le travail lent ensuite (dans une file d'attente, par exemple).

Nouvelles tentatives#

  • Répondez avec n'importe quel 2xx dans les 10 secondes. Tout le reste est un échec : un autre code de statut (4xx compris), aucune réponse dans les 10 secondes, ou aucune connexion dans les 3 secondes. Les redirections sont suivies, 3 au maximum, et chacune doit mener à une adresse publique.
  • Une livraison échouée est retentée après 1, 5 et 15 minutes, puis 1, 2, 4, 8 et 8 heures : 9 essais sur environ 23 heures. Après le dernier, cet événement est abandonné.
  • La nouvelle tentative a le même corps et le même WbizTool-Event-Id, avec un nouveau WbizTool-Timestamp et une nouvelle signature.
  • L'ordre n'est pas garanti. Un message.sent retenté peut arriver après un événement ultérieur. Utilisez le status du message, ou obtenez le message, quand l'ordre compte.

last_delivery sur l'endpoint montre comment s'est passée la dernière livraison : state (pending, delivered ou failed), le status_code qu'a répondu votre serveur, et quand l'événement a été créé.

Quand un endpoint s'arrête#

Un endpoint cesse de recevoir des événements dans l'un des cas suivants. Il reste dans GET /webhooks avec active: false et un disabled_reason, pour que vous puissiez voir pourquoi ; il peut encore être lu et supprimé, mais pas testé ni doté d'un nouveau secret (404).

  • Il échoue depuis 24 heures. Si rien ne lui a été livré pendant 24 heures alors que ses livraisons continuaient d'échouer, il est désactivé et ses nouvelles tentatives en attente sont abandonnées. Paramètres → Clés API l'affiche alors comme Désactivé. Réparez votre serveur, puis ajoutez l'endpoint de nouveau.
  • La clé qui l'a créé est révoquée ou expire. Révoquer une clé arrête tout ce qu'elle a mis en place. Ajoutez l'endpoint de nouveau avec une autre clé.
  • Il est supprimé, avec Supprimer un endpoint ou sur Paramètres → Clés API.

Les endpoints arrêtés ne comptent pas dans les 5.

L'objet Endpoint de webhook#

ChampTypeDescription
idintegerL'identifiant de l'endpoint.
urlstringOù les événements sont envoyés.
descriptionstringVotre note ; "" s'il n'y en a pas.
eventsarrayLes types d'événements qu'il reçoit.
secretstringLe secret de signature : uniquement dans les réponses à Ajouter un endpoint et Remplacer le secret de signature.
secret_last4stringLes 4 derniers caractères du secret, pour savoir lequel vous avez.
activebooleanfalse lorsqu'il ne reçoit plus d'événements – voir Quand un endpoint s'arrête.
disabled_reasonstring or nullPourquoi il s'est arrêté (échecs pendant 24 heures, ou la clé qui l'a créé a été révoquée ou a expiré) ; null tant qu'il est actif.
created_atstringQuand il a été ajouté.
last_deliveryobject or nullLa dernière livraison tentée : state, status_code (ou null quand votre serveur n'a pas répondu) et created_at. null avant la première.

Voir aussi#