Guide del prodotto
Webhook dei messaggi in arrivo (listener)
Un listener collega uno dei tuoi numeri WhatsApp a Unibox e può inviare un webhook per i messaggi in entrata al tuo server. Nella dashboard, i listener si gestiscono nella pagina Trigger in Ingresso. Quando un numero diventa listener, le sue chat vengono sincronizzate nella posta in arrivo Unibox e, se aggiungi un URL webhook, Wbiztool invia ogni nuovo messaggio al tuo server. Usa i webhook per registrare le conversazioni in un CRM, avvisare il tuo team o creare una risposta automatica.
Prima di iniziare#
- Il componente aggiuntivo Unibox. Costa $20/mese o $200/anno per numero WhatsApp, e il numero di listener che puoi avere è pari alla quantità del componente aggiuntivo. Acquistalo in Componenti Aggiuntivi Disponibili su Fatturazione e Piani all'interno della dashboard. Senza di esso, la pagina Trigger in Ingresso si apre comunque, ma facendo clic su Aggiungi Nuovo Listener compare Unibox Add-on Required (componente aggiuntivo Unibox richiesto) con un pulsante Subscribe to Unibox Add-on (abbonati al componente aggiuntivo Unibox).
- Un numero WhatsApp collegato in Impostazioni WhatsApp. Si possono aggiungere solo numeri collegati che non sono ancora listener.
- Devi essere proprietario o editor dello spazio di lavoro.
- Per i webhook: un URL pubblico (usa
https) di al massimo 100 caratteri che accetti richiestePOSTcon un corpo JSON.
Aggiungere un listener#
Apri Trigger in Ingresso
Nella barra laterale, apri UniBox e fai clic su Trigger in Ingresso, oppure vai a Trigger in Ingresso.
Crea un nuovo listener
Fai clic sulla scheda Aggiungi Nuovo Listener.
Scegli il numero
Selezionalo in Seleziona Numero WhatsApp. Se l'elenco mostra No available WhatsApp numbers (nessun numero WhatsApp disponibile), tutti i numeri collegati sono già listener oppure nessun numero è collegato.
Aggiungi un webhook (facoltativo)
Inserisci il tuo URL Webhook (Opzionale). Quando scrivi un URL, compare Webhook Events (eventi webhook): lascia selezionati Incoming Messages (messaggi in entrata), Outgoing Messages (messaggi in uscita) o entrambi. Puoi aggiungere o modificare l'URL in seguito.
Salva
Fai clic su Aggiungi Listener. Il listener compare come scheda con lo stato Active (attivo). Se hai inserito un URL, viene creato un secret webhook per quell'URL.
Gestire i listener#
Ogni scheda mostra il numero, il suo stato, l'URL Webhook: (oppure Non configurato), Ultima Attività: (quando è stata eseguita l'ultima verifica dei messaggi del numero, oppure Mai) e il Segreto Webhook:, nascosto finché non fai clic sul pulsante a forma di occhio.
Apri il menu ⋮ di una scheda per:
| Azione | Cosa succede |
|---|---|
| Modifica | Cambia l'URL webhook, gli eventi webhook o il secret. Il numero non si può cambiare. |
| Disabilita / Abilita | Disabilitando, il listener passa a Inactive (inattivo): il numero smette di sincronizzarsi con la posta in arrivo e non vengono inviati webhook. Abilitandolo torna Active. |
| Elimina | Rimuove il listener dopo la tua conferma. Le conversazioni già presenti nella posta in arrivo restano. Se in seguito aggiungi di nuovo lo stesso numero, il listener viene ripristinato. Se inserisci un URL webhook quando lo aggiungi di nuovo, il listener mantiene il suo secret webhook precedente, se ne aveva uno, invece di riceverne uno nuovo. |
Stati del listener#
| Stato | Significato |
|---|---|
| Active | I messaggi si sincronizzano e i webhook vengono inviati finché il numero è collegato. |
| Pending (in attesa) | Il numero non era collegato quando il listener è stato creato, ad esempio da Zapier. Fai clic su Abilita quando il numero è collegato. |
| Inactive | Disabilitato. Non si sincronizza nulla e non vengono inviati webhook. |
Statistiche#
| Riquadro | Cosa mostra |
|---|---|
| Listener Attivi | Tutti i listener della pagina, compresi quelli disabilitati. |
| Numeri Disponibili | Numeri collegati che non sono ancora listener. I numeri di cui hai eliminato il listener contano ancora come usati, quindi qui può comparire un valore inferiore a quanti puoi effettivamente aggiungerne. |
| Limite Totale | Quanti listener consente il tuo componente aggiuntivo Unibox. |
| Messaggi Oggi | Non ancora conteggiato; mostra sempre 0. |
Modificare l'URL o gli eventi del webhook#
Apri il listener
Fai clic su ⋮ nella scheda, poi su Modifica.
Aggiorna le impostazioni
Modifica Webhook URL (Optional) (URL webhook, facoltativo) e Webhook Events. Svuota l'URL per interrompere i webhook per questo numero: viene rimosso anche il suo secret, e se aggiungi di nuovo un URL ne viene creato uno nuovo.
Salva
Fai clic su Update Listener (aggiorna listener).
Rigenerare il secret#
In Edit Listener (modifica listener), fai clic sul pulsante di aggiornamento accanto a Webhook Secret e conferma. Il nuovo secret viene salvato subito, anche se poi chiudi la finestra senza fare clic su Update Listener, e da quel momento le richieste vengono firmate con il nuovo secret. Aggiorna immediatamente il tuo server con il nuovo secret.
Come vengono consegnati i webhook#
Wbiztool invia una richiesta POST al tuo URL per ogni nuovo messaggio trovato durante la sincronizzazione del numero, che avviene a intervalli di pochi minuti finché il numero è collegato e non è occupato a inviare messaggi.
- Eventi:
message_receivedper i messaggi che le persone inviano al tuo numero emessage_sentper i messaggi inviati dal tuo numero (dal telefono, dalle campagne o dall'API). Vengono inviati solo gli eventi selezionati in Webhook Events. - Le risposte inviate dalla posta in arrivo Unibox di solito non attivano
message_sent, perché la posta in arrivo le contiene già quando viene eseguita la sincronizzazione. - Risposta: rispondi con HTTP
200entro 8 secondi. Qualsiasi altra risposta o un timeout conta come consegna non riuscita. - Nessun nuovo tentativo: ogni messaggio viene inviato una sola volta. Se il tuo server non è raggiungibile, quel webhook va perso.
- Ordine: le richieste vengono inviate in modo indipendente e possono arrivare in ordine diverso. Ordina per
message.timestampse l'ordine è importante.
Header#
| Header | Valore |
|---|---|
Content-Type | application/json |
X-Wbiztool-Event | message_received o message_sent |
X-Wbiztool-Timestamp | Quando è stato inviato il webhook, in ISO 8601 UTC. Uguale a timestamp nel corpo. |
X-Wbiztool-Webhook-Id | L'ID del listener. Uguale a webhook_id nel corpo. |
X-Wbiztool-Signature | sha256= seguito dalla firma. Viene inviato ogni volta che il listener ha un secret, cosa che avviene sempre quando è impostato un URL. |
Payload#
{
"event": "message_received",
"timestamp": "2026-09-16T10:31:12.482913+00:00",
"webhook_id": 42,
"whatsapp_client_id": "678",
"whatsapp_phone": "919812345678",
"message": {
"id": "[email protected]_3EB0C1A2B3D4E5F60718",
"type": "chat",
"content": "Hi, is my order #4821 out for delivery?",
"from": "919876543210",
"from_name": "Aman",
"to": "919812345678",
"timestamp": "2026-09-16T10:29:58+00:00",
"whatsapp_timestamp": 1789554598,
"direction": "incoming",
"status": "pending",
"is_forwarded": false,
"forwarding_score": 0
},
"contact": {
"whatsapp_id": "[email protected]",
"phone": "919876543210",
"name": "Aman",
"is_group": false,
"is_business": false
},
"organisation": {
"id": "10314",
"name": "Acme Stores"
}
}{
"event": "message_sent",
"timestamp": "2026-09-16T10:33:40.117205+00:00",
"webhook_id": 42,
"whatsapp_client_id": "678",
"whatsapp_phone": "919812345678",
"message": {
"id": "[email protected]_3EB0F9E8D7C6B5A40312",
"type": "chat",
"content": "Yes, it will reach you today by 6 PM.",
"from": "919876543210",
"from_name": "Aman",
"to": "919812345678",
"timestamp": "2026-09-16T10:31:04+00:00",
"whatsapp_timestamp": 1789554664,
"direction": "outgoing",
"status": "pending",
"is_forwarded": false,
"forwarding_score": 0
},
"contact": {
"whatsapp_id": "[email protected]",
"phone": "919876543210",
"name": "Aman",
"is_group": false,
"is_business": false
},
"organisation": {
"id": "10314",
"name": "Acme Stores"
}
}{
"event": "message_received",
"timestamp": "2026-09-16T10:36:02.904311+00:00",
"webhook_id": 42,
"whatsapp_client_id": "678",
"whatsapp_phone": "919812345678",
"message": {
"id": "[email protected]_3EB0A7B6C5D4E3F20109",
"type": "image",
"content": "📸 Image",
"from": "919876543210",
"from_name": "Aman",
"to": "919812345678",
"timestamp": "2026-09-16T10:34:51+00:00",
"whatsapp_timestamp": 1789554891,
"direction": "incoming",
"status": "pending",
"is_forwarded": false,
"forwarding_score": 0,
"media": {
"filename": "",
"mimetype": "image/jpeg",
"size": 245760
}
},
"contact": {
"whatsapp_id": "[email protected]",
"phone": "919876543210",
"name": "Aman",
"is_group": false,
"is_business": false
},
"organisation": {
"id": "10314",
"name": "Acme Stores"
}
}{
"event": "message_received",
"timestamp": "2026-09-16T10:40:15.330187+00:00",
"webhook_id": 42,
"whatsapp_client_id": "678",
"whatsapp_phone": "919812345678",
"message": {
"id": "[email protected][email protected]",
"type": "chat",
"content": "Is the store open on Sunday?",
"from": "120363041234567890",
"from_name": "Acme Loyalty Club",
"to": "919812345678",
"timestamp": "2026-09-16T10:39:02+00:00",
"whatsapp_timestamp": 1789555142,
"direction": "incoming",
"status": "pending",
"is_forwarded": false,
"forwarding_score": 0
},
"contact": {
"whatsapp_id": "[email protected]",
"phone": "120363041234567890",
"name": "Acme Loyalty Club",
"is_group": true,
"is_business": false
},
"organisation": {
"id": "10314",
"name": "Acme Stores"
},
"group": {
"name": "Acme Loyalty Club"
}
}Numeri, ID e nomi qui sopra sono esempi.
Campi di primo livello#
| Campo | Tipo | Descrizione |
|---|---|---|
event | string | message_received o message_sent. |
timestamp | string | Quando è stato inviato il webhook (ISO 8601, UTC). |
webhook_id | integer | L'ID del listener. |
whatsapp_client_id | string | ID del tuo numero WhatsApp, come mostrato in Impostazioni WhatsApp. |
whatsapp_phone | string | Il tuo numero WhatsApp. |
message | object | Il messaggio. Vedi sotto. |
contact | object | La persona o il gruppo con cui si svolge la conversazione. Vedi sotto. |
organisation | object | id (string) e name del tuo spazio di lavoro. |
group | object | Solo per le chat di gruppo: name, il nome del gruppo. |
Campi di message#
| Campo | Tipo | Descrizione |
|---|---|---|
id | string | L'ID assegnato da WhatsApp al messaggio. Usalo per ignorare i duplicati. |
type | string | chat per il testo. Altrimenti il tipo di WhatsApp, ad esempio image, video, audio, ptt (nota vocale), document, sticker o location. |
content | string | Il testo per i messaggi chat. Per i contenuti multimediali, invece, un'etichetta: 📸 Image, 🎥 Video, 🎵 Audio, 🎤 Voice Message, 😊 Sticker, Document: seguito dal testo del documento (Document quando non c'è testo), oppure il nome del tipo con l'iniziale maiuscola per tutto il resto, come Location. Le didascalie non sono incluse. |
from | string | Sempre il numero del contatto (o l'ID del gruppo), in entrambe le direzioni. |
from_name | string | Il nome del contatto o del gruppo. Può essere vuoto. |
to | string | Sempre il tuo numero WhatsApp, in entrambe le direzioni. |
timestamp | string | Quando il messaggio è stato inviato su WhatsApp (ISO 8601, UTC). |
whatsapp_timestamp | integer | Lo stesso orario come timestamp Unix in secondi. |
direction | string | incoming o outgoing. Usa questo campo, non from e to, per capire la direzione. |
status | string | Attualmente sempre pending. Non farci affidamento per lo stato di consegna o di lettura. |
is_forwarded | boolean | Se il messaggio è stato inoltrato. |
forwarding_score | integer | Quante volte è stato inoltrato. |
media | object | Per i messaggi multimediali, quando i dettagli sono disponibili: filename, mimetype e size in byte. Il file stesso non è incluso. |
quoted_message_id | string | Solo quando il messaggio risponde a un altro messaggio. |
Campi di contact#
| Campo | Tipo | Descrizione |
|---|---|---|
whatsapp_id | string | L'ID WhatsApp, ad esempio [email protected] per una persona o …@g.us per un gruppo. |
phone | string | Il numero senza +, oppure l'ID del gruppo per i gruppi. |
name | string | Il nome che Wbiztool ha per il contatto, oppure il nome del gruppo. Può essere vuoto. |
is_group | boolean | true per le chat di gruppo. |
is_business | boolean | true per gli account WhatsApp Business, quando noto. |
Verificare la firma#
Ogni richiesta è firmata con il secret del tuo listener tramite HMAC-SHA256. La firma è calcolata sul corpo grezzo della richiesta esattamente come ricevuto e inviata in X-Wbiztool-Signature come sha256= seguito dal digest esadecimale in minuscolo.
Calcola sempre la firma dai byte grezzi prima di analizzare il JSON. Analizzare e ricodificare il corpo lo modifica (ad esempio, i caratteri non inglesi e le emoji arrivano con escape come \uXXXX) e la firma non corrisponderà.
// Express: keep the raw body for this route
import crypto from "node:crypto";
import express from "express";
const app = express();
const SECRET = process.env.WBIZTOOL_WEBHOOK_SECRET;
app.post("/wbiztool/webhook", express.raw({ type: "application/json" }), (req, res) => {
const expected = "sha256=" + crypto.createHmac("sha256", SECRET).update(req.body).digest("hex");
const received = req.get("X-Wbiztool-Signature") || "";
const valid =
received.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
if (!valid) return res.status(401).send("Invalid signature");
const data = JSON.parse(req.body.toString("utf8"));
if (data.event === "message_received") {
console.log(`New message from ${data.contact.phone}: ${data.message.content}`);
}
res.sendStatus(200); // reply quickly; do slow work in the background
});
app.listen(3000);# Flask
import hashlib
import hmac
import json
import os
from flask import Flask, abort, request
app = Flask(__name__)
SECRET = os.environ["WBIZTOOL_WEBHOOK_SECRET"].encode()
@app.post("/wbiztool/webhook")
def wbiztool_webhook():
raw_body = request.get_data() # raw bytes, before parsing
expected = "sha256=" + hmac.new(SECRET, raw_body, hashlib.sha256).hexdigest()
received = request.headers.get("X-Wbiztool-Signature", "")
if not hmac.compare_digest(expected, received):
abort(401)
data = json.loads(raw_body)
if data["event"] == "message_received":
print(f"New message from {data['contact']['phone']}: {data['message']['content']}")
return "", 200<?php
$secret = getenv('WBIZTOOL_WEBHOOK_SECRET');
$rawBody = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
$received = $_SERVER['HTTP_X_WBIZTOOL_SIGNATURE'] ?? '';
if (!hash_equals($expected, $received)) {
http_response_code(401);
exit('Invalid signature');
}
$data = json_decode($rawBody, true);
if ($data['event'] === 'message_received') {
error_log('New message from ' . $data['contact']['phone'] . ': ' . $data['message']['content']);
}
http_response_code(200);La firma non copre l'header del timestamp, quindi non protegge dal replay di una richiesta. Se per te è importante, memorizza ogni message.id che hai elaborato e ignora le ripetizioni.
Risoluzione dei problemi#
| Messaggio o problema | Cosa fare |
|---|---|
| Unibox Add-on Required quando fai clic su Aggiungi Nuovo Listener | Il tuo spazio di lavoro non ha il componente aggiuntivo Unibox. Acquistalo in Componenti Aggiuntivi Disponibili su Fatturazione e Piani all'interno della dashboard. |
You have reached your unibox numbers limit | Hai raggiunto il limite di numeri Unibox. Elimina un listener che non ti serve più oppure aumenta la quantità del componente aggiuntivo. |
| No available WhatsApp numbers | Collega un altro numero, oppure il numero è già un listener. |
This WhatsApp number is already a listener | Il numero è già un listener. Modifica la scheda esistente. |
Invalid WhatsApp client | Il numero si è scollegato. Ricollegalo in Impostazioni WhatsApp e ricarica la pagina. |
Errore che menziona value too long durante il salvataggio | L'URL webhook supera i 100 caratteri. Usa un URL più breve. |
| Non arriva nessun webhook | Verifica che il listener sia Active, che il numero sia collegato, che il tipo di evento sia selezionato e che il tuo URL sia pubblico, in https e con un certificato valido. I messaggi vengono inviati solo dopo la sincronizzazione successiva, qualche minuto più tardi. |
| Mancano alcuni webhook | Il tuo server ha restituito una risposta diversa da 200, ha impiegato più di 8 secondi o non era raggiungibile. Le consegne non riuscite non vengono ritentate. |
| La firma non corrisponde | Usa il corpo grezzo, non il JSON ricodificato, e il secret attuale. Rigenerare il secret, o collegare Zapier, lo sostituisce. |
| Ultima Attività: mostra Mai | Il numero non è ancora stato verificato. Deve essere collegato e non occupato a inviare messaggi. |
