API v2 (Beta)
Webhooks
Ein Webhook-Endpunkt ist eine URL auf Ihrem Server, die WbizTool aufruft, wenn mit einer Nachricht etwas passiert: Sie wurde gesendet, ist fehlgeschlagen, wurde storniert oder ist abgelaufen. Sie erfahren es sofort, statt regelmäßig abzufragen, und jeder Request ist signiert, damit Sie prüfen können, dass er von WbizTool kommt.
| Vorgang | Endpoint | Scope |
|---|---|---|
| Endpunkt hinzufügen | POST /api/v2/webhooks | messages:read |
| Endpunkte auflisten | GET /api/v2/webhooks | messages:read |
| Einen Endpunkt abrufen | GET /api/v2/webhooks/{id} | messages:read |
| Endpunkt entfernen | DELETE /api/v2/webhooks/{id} | messages:read |
| Signatur-Secret ersetzen | POST /api/v2/webhooks/{id}/rotate-secret | messages:read |
| Testereignis senden | POST /api/v2/webhooks/{id}/test | messages:read |
Endpunkte gehören zum Arbeitsbereich und erhalten Ereignisse für alle seine Nachrichten, egal was sie gesendet hat: die API, das Dashboard, Kampagnen, Erinnerungen oder Integrationen. Deshalb kann ein auf einige Nummern beschränkter Schlüssel sie nicht verwalten: Jeder Aufruf antwortet mit 403 number_not_allowed.
Ereignisse#
| Typ | Wann |
|---|---|
message.sent | Eine Nachricht ist von Ihrer Nummer rausgegangen. |
message.failed | Eine Nachricht konnte nicht gesendet werden; error.message nennt den Grund. |
message.cancelled | Eine wartende Nachricht wurde storniert: über die API, im Dashboard oder weil ihre Nummer oder Kampagne entfernt wurde. |
message.expired | Eine Nachricht wurde nicht vor ihrem expires_at gesendet. |
ping | Ein von Ihnen angefordertes Testereignis. |
Ereignisse für Nummern (verbunden, abgemeldet) und für eingehende Nachrichten folgen später. Zugestellt- und Gelesen-Häkchen lösen keine Ereignisse aus; rufen Sie die Nachricht ab, um sie zu sehen.
Endpunkt hinzufügen#
https://wbiztool.com/api/v2/webhooksBody: JSON (application/json)
Body
urlstringerforderlichIhr Endpunkt: eine öffentliche
https://-Adresse, bis zu 500 Zeichen.http://, private und lokale Adressen (zum Beispiellocalhostoder192.168.x.x) werden abgelehnt.eventsarray of stringsoptionalDie Ereignistypen, die er erhält: beliebige von
message.sent,message.failed,message.cancelledundmessage.expired. Standard: alle vier.pingkommt immer an, wenn Sie eines anfordern.descriptionstringoptionalEine Notiz für Sie selbst, bis zu 100 Zeichen, zum Beispiel
Order system.
Ein Arbeitsbereich kann bis zu 5 Endpunkte haben. Die Antwort ist 201 mit dem Endpunkt und seinem Signatur-secret, das nur dieses eine Mal angezeigt wird. Speichern Sie es dort, wo Ihr Empfänger es lesen kann, zum Beispiel in einer Umgebungsvariablen. Spätere Antworten zeigen nur secret_last4; wenn Sie das Secret verlieren, ersetzen Sie es.
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"
}'import os
import requests
response = requests.post(
"https://wbiztool.com/api/v2/webhooks",
headers={"Authorization": f"Bearer {os.environ['WBIZTOOL_API_KEY']}"},
json={
"url": "https://example.com/wbiztool/events",
"events": ["message.sent", "message.failed"],
"description": "Order system",
},
timeout=30,
)
endpoint = response.json()
print("Store this secret now:", endpoint["secret"])// Node.js 18+ (built-in fetch). Save as .mjs to use top-level await.
const response = await fetch("https://wbiztool.com/api/v2/webhooks", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.WBIZTOOL_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
url: "https://example.com/wbiztool/events",
events: ["message.sent", "message.failed"],
description: "Order system",
}),
});
const endpoint = await response.json();
console.log("Store this secret now:", endpoint.secret);<?php
$ch = curl_init('https://wbiztool.com/api/v2/webhooks');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('WBIZTOOL_API_KEY'), 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'url' => 'https://example.com/wbiztool/events',
'events' => ['message.sent', 'message.failed'],
'description' => 'Order system',
]),
CURLOPT_RETURNTRANSFER => true,
]);
$endpoint = json_decode(curl_exec($ch), true);
curl_close($ch);
echo 'Store this secret now: ' . $endpoint['secret'];Antwort (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"
}
Das echte secret besteht aus 64 Hexadezimalzeichen. Eine Wiederholung dieses Requests mit demselben Idempotency-Key fügt keinen zweiten Endpunkt hinzu, und ihre Antwort lässt das Secret weg.
Fehler beim Hinzufügen eines Endpunkts#
| HTTP | Code | Wann |
|---|---|---|
400 | validation_failed | url fehlt oder ist zu lang, events ist leer oder enthält einen unbekannten Typ, oder ein Feld ist unbekannt |
403 | webhook_limit_reached | Der Arbeitsbereich hat bereits 5 Endpunkte |
403 | number_not_allowed | Der Schlüssel ist auf einige Nummern beschränkt |
422 | invalid_webhook_url | url ist keine öffentliche https://-Adresse |
Endpunkte auflisten#
https://wbiztool.com/api/v2/webhooksDie Endpunkte des Arbeitsbereichs, die Ereignisse erhalten, der älteste zuerst, in einer einzigen data-Liste.
curl https://wbiztool.com/api/v2/webhooks \
-H "Authorization: Bearer $WBIZTOOL_API_KEY"
Die Liste lässt Endpunkte weg, die gestoppt wurden. Inhaber sehen die Endpunkte auch unter Einstellungen → API-Schlüssel bei Webhook-Endpunkte und können sie dort entfernen.
Einen Endpunkt abrufen#
https://wbiztool.com/api/v2/webhooks/{id}Ein einzelner Endpunkt. last_delivery zeigt, wie seine letzte Zustellung verlaufen ist. Ein Endpunkt, der nicht existiert oder entfernt wurde, antwortet mit 404 webhook_not_found; ein gestoppter wird mit active: false angezeigt.
Endpunkt entfernen#
https://wbiztool.com/api/v2/webhooks/{id}An ihn wird nichts mehr gesendet, auch keine noch wartenden Wiederholungen. Die Antwort ist {"id": 41, "deleted": true}.
Signatur-Secret ersetzen#
https://wbiztool.com/api/v2/webhooks/{id}/rotate-secretErstellt ein neues Secret für den Endpunkt. Die Antwort ist 200 mit dem Endpunkt und seinem neuen secret, das nur dieses eine Mal angezeigt wird. Das alte Secret funktioniert sofort nicht mehr: Danach gesendete Ereignisse werden mit dem neuen signiert, auch Wiederholungen früherer Ereignisse. Aktualisieren Sie Ihren Empfänger sofort.
curl -X POST https://wbiztool.com/api/v2/webhooks/41/rotate-secret \
-H "Authorization: Bearer $WBIZTOOL_API_KEY"
Testereignis senden#
https://wbiztool.com/api/v2/webhooks/{id}/testStellt ein ping-Ereignis an diesen Endpunkt in die Warteschlange, signiert wie jedes Ereignis, damit Sie Ihren Empfänger testen können. Die Antwort ist 202:
{ "event_id": "evt_8a2f4c1d9b7e6a5f4c3b2a19", "type": "ping", "queued": true }
Ihr Endpunkt erhält:
{
"id": "evt_8a2f4c1d9b7e6a5f4c3b2a19",
"type": "ping",
"created_at": "2026-10-18T11:21:40.002Z",
"data": { "webhook_id": 41 }
}
Die Ereignis-Payload#
Jedes Ereignis ist ein POST mit einem JSON-Body:
{
"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
}
}
}
| Feld | Beschreibung |
|---|---|
id | Die ID des Ereignisses, evt_ und 24 Zeichen. Dasselbe Ereignis behält seine ID bei jeder Wiederholung. |
type | Einer der Ereignistypen. |
created_at | Wann das Ereignis passiert ist. |
data.message | Bei message.*-Ereignissen: die Nachricht in ihrem damaligen Zustand. Eine darin enthaltene media.url funktioniert mindestens 26 Stunden. |
data.webhook_id | Bei ping: die ID des Endpunkts. |
Header:
| Header | Wert |
|---|---|
Content-Type | application/json |
User-Agent | WbizTool-Webhooks/2 |
WbizTool-Event-Id | Die id des Ereignisses, bei jeder Wiederholung dieselbe |
WbizTool-Timestamp | Wann dieser Versuch gesendet wurde, in Unix-Sekunden, zum Beispiel 1792310409 |
WbizTool-Signature | v1= und der hexadezimale HMAC-SHA256 von <timestamp>.<raw body>, erstellt mit dem Secret des Endpunkts |
Signatur prüfen#
Prüfen Sie jeden Request, bevor Sie ihm vertrauen: Berechnen Sie die Signatur aus dem unveränderten Body (bevor Ihr Framework das JSON parst), vergleichen Sie sie in konstanter Zeit mit dem Header und lehnen Sie Zeitstempel ab, die mehr als 5 Minuten abweichen, damit ein alter Request nicht erneut eingespielt werden kann. Es ist dasselbe Verfahren wie bei den Webhook-Signaturen der API v1, mit dem eigenen Secret des Endpunkts.
Verwenden Sie dann WbizTool-Event-Id, um Wiederholungen zu verwerfen: Dasselbe Ereignis kann mehr als einmal ankommen, zum Beispiel wenn Ihre Antwort verloren ging und wir es erneut versucht haben.
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 "", 200In Django übergeben Sie request.body und request.headers.
const crypto = require("crypto");
const express = require("express");
const SECRET = process.env.WBIZTOOL_WEBHOOK_SECRET;
function verify(secret, rawBody, headers) {
const ts = String(headers["wbiztool-timestamp"] || ""), sig = String(headers["wbiztool-signature"] || "");
if (!/^\d+$/.test(ts) || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const mac = "v1=" + crypto.createHmac("sha256", secret).update(ts + ".").update(rawBody).digest("hex");
return sig.length === mac.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(mac));
}
const app = express();
const seen = new Set(); // use your database in production
// express.raw keeps the body as a Buffer: the signature covers the exact bytes.
app.post("/wbiztool/events", express.raw({ type: "*/*" }), (req, res) => {
if (!verify(SECRET, req.body, req.headers)) return res.sendStatus(401);
const eventId = req.headers["wbiztool-event-id"];
if (seen.has(eventId)) return res.sendStatus(200); // a repeat: already handled
seen.add(eventId);
const event = JSON.parse(req.body);
if (event.type === "message.failed") {
const message = event.data.message;
console.log("Failed:", message.id, message.error.message);
}
res.sendStatus(200);
});
app.listen(3000);Antworten Sie schnell mit 2xx und erledigen Sie langsame Arbeit danach (zum Beispiel in einer Warteschlange).
Wiederholungen#
- Antworten Sie innerhalb von 10 Sekunden mit einem beliebigen
2xx. Alles andere ist ein Fehlschlag: ein anderer Statuscode (4xxeingeschlossen), keine Antwort innerhalb von 10 Sekunden oder keine Verbindung innerhalb von 3. Weiterleitungen werden verfolgt, höchstens 3, und jede muss zu einer öffentlichen Adresse führen. - Eine fehlgeschlagene Zustellung wird erneut versucht, nach 1, 5 und 15 Minuten, dann nach 1, 2, 4, 8 und 8 Stunden: 9 Versuche über etwa 23 Stunden. Nach dem letzten wird das Ereignis verworfen.
- Die Wiederholung hat denselben Body und dieselbe
WbizTool-Event-Id, mit einem neuenWbizTool-Timestampund einer neuen Signatur. - Die Reihenfolge ist nicht garantiert. Ein wiederholtes
message.sentkann nach einem späteren Ereignis ankommen. Verwenden Sie denstatusder Nachricht oder rufen Sie die Nachricht ab, wenn die Reihenfolge wichtig ist.
last_delivery am Endpunkt zeigt, wie die letzte Zustellung verlaufen ist: state (pending, delivered oder failed), der status_code, mit dem Ihr Server geantwortet hat, und wann das Ereignis erstellt wurde.
Wann Endpunkte gestoppt werden#
Ein Endpunkt erhält keine Ereignisse mehr, wenn einer dieser Fälle eintritt. Er bleibt in GET /webhooks mit active: false und einem disabled_reason, damit Sie den Grund sehen; er kann weiterhin gelesen und entfernt, aber nicht getestet werden und kein neues Secret erhalten (404).
- Er schlägt seit 24 Stunden fehl. Wurde 24 Stunden lang nichts an ihn zugestellt, während seine Zustellungen weiter fehlschlugen, wird er abgeschaltet, und seine wartenden Wiederholungen werden verworfen. Einstellungen → API-Schlüssel zeigt ihn dann als Aus. Beheben Sie das Problem auf Ihrem Server und fügen Sie den Endpunkt dann erneut hinzu.
- Der Schlüssel, der ihn erstellt hat, wird widerrufen oder läuft ab. Wird ein Schlüssel widerrufen, stoppt alles, was er eingerichtet hat. Fügen Sie den Endpunkt mit einem anderen Schlüssel erneut hinzu.
- Er wird entfernt, mit Endpunkt entfernen oder unter Einstellungen → API-Schlüssel.
Gestoppte Endpunkte zählen nicht zu den 5.
Das Webhook-endpoint-Objekt#
| Feld | Typ | Beschreibung |
|---|---|---|
id | integer | Die ID des Endpunkts. |
url | string | Wohin Ereignisse gesendet werden. |
description | string | Ihre Notiz; "", wenn es keine gibt. |
events | array | Die Ereignistypen, die er erhält. |
secret | string | Das Signatur-Secret: nur in den Antworten auf Endpunkt hinzufügen und Signatur-Secret ersetzen. |
secret_last4 | string | Die letzten 4 Zeichen des Secrets, damit Sie erkennen, welches Sie haben. |
active | boolean | false, wenn er keine Ereignisse mehr erhält – siehe Wann Endpunkte gestoppt werden. |
disabled_reason | string or null | Warum er gestoppt wurde (24 Stunden lang fehlgeschlagen, oder der Schlüssel, der ihn erstellt hat, wurde widerrufen oder ist abgelaufen); null, solange er aktiv ist. |
created_at | string | Wann er hinzugefügt wurde. |
last_delivery | object or null | Die letzte versuchte Zustellung: state, status_code (oder null, wenn Ihr Server nicht geantwortet hat) und created_at. null vor der ersten. |
Verwandte Themen#
- Nachrichten: das Message-Objekt in
data.message - Webhook-Signaturen für die API v1
- Fehler
