Webhook
I webhook inviano gli eventi del centralino ai tuoi sistemi con una richiesta HTTPS firmata: nessuna connessione da mantenere aperta. Scope webhooks:read / webhooks:write; massimo 10 webhook per azienda.
Creare un webhook
curl -s -X POST https://centralino.exovoip.it/api/v1/webhooks \
-H "X-API-Key: $EXOPBX_API_KEY" -H "Content-Type: application/json" \
-d '{"name":"CRM","url":"https://crm.example.com/exopbx","events":["channel.hangup","calendar.override"]}'
curl -s -X POST https://centralino.exovoip.it/api/v1/webhooks/2/test -H "X-API-Key: $EXOPBX_API_KEY"const { webhook, secret } = await pbx.webhooks.create({
name: 'CRM',
url: 'https://crm.example.com/exopbx',
events: ['channel.hangup', 'calendar.override'], // oppure ['*']
});
// secret (whsec_...) è mostrato solo ora: serve a verificare la firma
const { delivery } = await pbx.webhooks.test(webhook.id);$out = $pbx->webhooks->create([
'name' => 'CRM',
'url' => 'https://crm.example.com/exopbx',
'events' => ['channel.hangup', 'calendar.override'],
]);
$secret = $out['secret'];
$pbx->webhooks->test($out['webhook']['id']);out = pbx.webhooks.create({
"name": "CRM",
"url": "https://crm.example.com/exopbx",
"events": ["channel.hangup", "calendar.override"],
})
secret = out["secret"]
pbx.webhooks.test(out["webhook"]["id"])events: tipi di evento (gli stessi del tempo reale) oppure["*"]per tutti. L'elenco dei tipi disponibili è inGET /v1/webhooks/event-types.- Il segreto
whsec_…è mostrato solo alla creazione e alla rotazione (POST /v1/webhooks/:id/rotate-secret). POST /v1/webhooks/:id/testinvia subito un eventowebhook.teste restituisce l'esito.
Richiesta inviata
POST all'indirizzo del webhook, corpo JSON:
{
"id": "7f1c1f4e-2c1b-4d4e-9d61-0b8a2b8e6c55",
"type": "channel.hangup",
"tenantId": 3,
"createdAt": "2026-11-04T10:00:00.000Z",
"data": { "type": "channel.hangup", "uniqueid": "1762250400.42", "durationS": 95 }
}| Header | Contenuto |
|---|---|
X-ExoPBX-Signature | t=<unix>,v1=<hmac>: firma da verificare |
X-ExoPBX-Timestamp | Momento dell'invio (secondi Unix) |
X-ExoPBX-Event | Tipo di evento |
X-ExoPBX-Delivery | Id dell'evento, uguale in tutti i tentativi: usalo per scartare i duplicati |
User-Agent | ExoPBX-Webhook/1.0 |
Verificare la firma
La firma è l'HMAC-SHA256, in esadecimale, del testo <t>.<corpo grezzo> calcolato con il segreto del webhook. Rifiuta le richieste con timestamp più vecchio di 5 minuti. Usa il corpo così come ricevuto: ri-serializzare il JSON cambia la firma.
# Verifica manuale con openssl: firma = HMAC-SHA256("<t>.<corpo>")
printf '%s.%s' "$T" "$BODY" | openssl dgst -sha256 -hmac "$EXOPBX_WEBHOOK_SECRET" -heximport { constructWebhookEvent } from '@exobyte/exopbx-sdk';
// rawBody: corpo grezzo della richiesta (stringa o Uint8Array), non il JSON ri-serializzato
const event = await constructWebhookEvent({
payload: rawBody,
signature: request.headers['x-exopbx-signature'],
secret: process.env.EXOPBX_WEBHOOK_SECRET!,
});
console.log(event.id, event.type, event.data);use Exobyte\ExoPBX\Webhook;
$event = Webhook::constructEvent(
file_get_contents('php://input'),
$_SERVER['HTTP_X_EXOPBX_SIGNATURE'] ?? null,
getenv('EXOPBX_WEBHOOK_SECRET'),
); // WebhookSignatureException se la firma non è validafrom exopbx import construct_event
event = construct_event(raw_body, headers.get("X-ExoPBX-Signature"), os.environ["EXOPBX_WEBHOOK_SECRET"])Esempi completi di endpoint (Node, Flask, PHP) sono negli archivi degli SDK, cartella examples.
Consegna e nuovi tentativi
- Successo: qualsiasi risposta 2xx entro 10 secondi. Rispondi subito ed elabora l'evento in seguito.
- Altrimenti nuovi tentativi dopo 30 s, 2 min, 8 min, 32 min, circa 2 h, 6 h e 6 h (8 tentativi in tutto); poi la consegna resta
failede si può ripetere dal registro. - I redirect non vengono seguiti; l'indirizzo deve essere pubblico (
httpohttps, consigliatohttps). - Riattivare un webhook disattivato per errori consecutivi azzera il contatore.
curl -s "https://centralino.exovoip.it/api/v1/webhooks/2/deliveries?status=failed" -H "X-API-Key: $EXOPBX_API_KEY"
curl -s -X POST https://centralino.exovoip.it/api/v1/webhooks/2/deliveries/918/retry -H "X-API-Key: $EXOPBX_API_KEY"
curl -s -X POST https://centralino.exovoip.it/api/v1/webhooks/2/rotate-secret -H "X-API-Key: $EXOPBX_API_KEY"const failed = await pbx.webhooks.deliveries(2, { status: 'failed' });
await pbx.webhooks.redeliver(2, 918);
const { secret } = await pbx.webhooks.rotateSecret(2);$failed = $pbx->webhooks->deliveries(2, status: 'failed');
$pbx->webhooks->redeliver(2, 918);
$secret = $pbx->webhooks->rotateSecret(2)['secret'];failed = pbx.webhooks.deliveries(2, status="failed")
pbx.webhooks.redeliver(2, 918)
secret = pbx.webhooks.rotate_secret(2)["secret"]Il registro delle consegne è conservato 30 giorni.
Endpoint
| Descrizione | Richiesta | Scope | Metodo SDK |
|---|---|---|---|
| Tipi di evento sottoscrivibili | GET /v1/webhooks/event-types | webhooks:read | webhooks.eventTypes() · py: webhooks.event_types() |
| Elenco webhook (piattaforma senza X-Tenant-Id) | GET /v1/webhooks | webhooks:read | webhooks.list() |
| Crea webhook (segreto di firma mostrato una sola volta) | POST /v1/webhooks | webhooks:write | webhooks.create() |
| Dettaglio webhook | GET /v1/webhooks/:id | webhooks:read | webhooks.get() |
| Modifica webhook | PATCH /v1/webhooks/:id | webhooks:write | webhooks.update() |
| Elimina webhook e storico invii | DELETE /v1/webhooks/:id | webhooks:write | webhooks.delete() |
| Nuovo segreto di firma (mostrato una sola volta) | POST /v1/webhooks/:id/rotate-secret | webhooks:write | webhooks.rotateSecret() · py: webhooks.rotate_secret() |
| Invio di prova (evento webhook.test) | POST /v1/webhooks/:id/test | webhooks:write | webhooks.test() |
| Registro invii (esito, codice risposta, tentativi) | GET /v1/webhooks/:id/deliveries | webhooks:read | webhooks.deliveries() |
| Nuovo invio di un evento già registrato | POST /v1/webhooks/:id/deliveries/:deliveryId/retry | webhooks:write | webhooks.redeliver() |
