Webhook
Ogni cliente può avere un URL di webhook, che riceve una POST JSON per ogni evento.
curl -X PUT https://api.depalmalabs.it/v1/customers/cus_xxxxxxxxxxxx/webhook \
-H "Authorization: Bearer $DPL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://tuo-gestionale.it/whatsapp/webhook"}'
# Il secret (whsec_...) è restituito solo alla prima configurazione o con "rotate_secret": trueEventi
| Tipo | Quando |
|---|---|
message.status | Cambio di stato di un messaggio inviato: sent, delivered, read, failed. Include categoria e se è a pagamento. |
message.received | Messaggio in arrivo dal destinatario (testo, media, risposta a pulsante). Apre la finestra di 24 ore. |
template.status | Template approvato, rifiutato (con motivo), in pausa o disattivato da Meta. |
customer.quality | Variazione della qualità del numero (GREEN, YELLOW, RED) e del limite di invio. Con RED il marketing va in pausa automatica. |
customer.status | Cliente collegato, in pausa, bloccato o di nuovo attivo; pagamento Meta mancante o ripristinato. |
Firma
Ogni richiesta ha l'header X-DPL-Signature: t=<unix>,v1=<hex>, dove v1 è l'HMAC-SHA256 di t + "." + corpo calcolato con il secret del webhook. Verifica la firma sul corpo grezzo, confronta in tempo costante e rifiuta timestamp più vecchi di 5 minuti.
<?php
// Webhook De Palma Labs: verifica firma, poi elabora. Rispondi 2xx entro 10 secondi.
$secret = getenv('DPL_WEBHOOK_SECRET'); // whsec_...
if (!$secret) { http_response_code(500); exit; } // senza secret ogni firma sarebbe falsificabile
$body = file_get_contents('php://input');
$header = $_SERVER['HTTP_X_DPL_SIGNATURE'] ?? '';
parse_str(str_replace(',', '&', $header), $parts); // t=...&v1=...
$t = (int)($parts['t'] ?? 0);
$expected = hash_hmac('sha256', $t . '.' . $body, $secret);
if (!$t || abs(time() - $t) > 300 || !hash_equals($expected, (string)($parts['v1'] ?? ''))) {
http_response_code(401);
exit;
}
$event = json_decode($body, true);
// Deduplica: lo stesso evento può arrivare più di una volta (consegna "almeno una volta").
// if (giaElaborato($event['id'])) { http_response_code(200); exit; }
switch ($event['type']) {
case 'message.status': // sent, delivered, read, failed
aggiornaStato($event['data']['message_id'], $event['data']['status']);
break;
case 'message.received': // risposta del cliente
salvaRisposta($event['data']['from'], $event['data']['text']);
break;
case 'template.status': // APPROVED, REJECTED...
aggiornaTemplate($event['data']['name'], $event['data']['status']);
break;
case 'customer.quality': // GREEN, YELLOW, RED e limite di invio
case 'customer.status': // attivo, in pausa, pagamento Meta mancante...
aggiornaCliente($event['customer_id'], $event['type'], $event['data']);
break;
}
http_response_code(200);Consegna e tentativi
- Rispondi con un codice 2xx entro 10 secondi; se ti serve più tempo, elabora in coda.
- In caso di errore ritentiamo dopo 30 s, 2 min, 10 min, 30 min, 1 h, 3 h, 6 h e 12 h (circa un giorno in tutto). Dopo l'ultimo tentativo l'evento resta visibile nel portale come non consegnato.
- Consegna "almeno una volta": deduplica sull'
iddell'evento. L'ordine non è garantito: usacreated_ate tieni lo stato più avanzato. - Solo URL https pubblici (porta 443 o 8443). I redirect non vengono seguiti.