Webhook
Ricevi notifiche in tempo reale quando lo stato del messaggio cambia. I webhook sono più veloci e affidabili del polling.
Webhook vs Polling
| Metodo | Velocità | Affidabilità | Caso d'Uso |
|---|---|---|---|
| Webhook | Istantaneo | Alto (il tuo endpoint riceve eventi) | Sistemi in produzione, elaborazione in tempo reale |
| Polling | Ritardato (verifica ogni N secondi) | Dipende dalla frequenza | Integrazioni a basso volume, semplici |
Consiglio: Usa webhook per la produzione.
Configurazione Webhook
Registra un Endpoint Webhook
Puoi gestire i webhook tramite l'API o la dashboard.
Endpoint API: POST /webhooks/delivery-status
Registra il tuo webhook:
- cURL
- Python
- Node.js
curl -X POST https://lora-api.agiletelecom.com/api/webhooks/delivery-status \
-H "X-Api-Key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-domain.com/webhook",
"method": "POST"
}'
import requests
headers = {
"X-Api-Key": "your_api_key_here",
"Content-Type": "application/json"
}
payload = {
"url": "https://your-domain.com/webhook",
"method": "POST"
}
response = requests.post(
"https://lora-api.agiletelecom.com/api/webhooks/delivery-status",
json=payload,
headers=headers
)
print(response.json())
const axios = require('axios');
const headers = {
'X-Api-Key': 'your_api_key_here',
'Content-Type': 'application/json'
};
const payload = {
url: 'https://your-domain.com/webhook',
method: 'POST'
};
axios.post(
'https://lora-api.agiletelecom.com/api/webhooks/delivery-status',
payload,
{ headers }
).then(response => console.log(response.data))
.catch(error => console.error(error));
Limitazione Importante
Un webhook attivo per account. Se registri un nuovo webhook, il precedente è sostituito.
Payload Webhook
Quando lo stato di un messaggio cambia, Qlara invia una richiesta POST al tuo endpoint:
{
"messageId": "msg_1234567890",
"customerMessageId": "your_reference_id",
"phoneNumber": "+393901234567",
"status": "DELIVERED",
"timestamp": "2025-04-08T14:30:00Z",
"channel": "sms",
"error": null
}
Campi del Payload
| Campo | Tipo | Descrizione |
|---|---|---|
messageId | string | ID messaggio Qlara univoco |
customerMessageId | string | Il tuo ID di riferimento (se fornito) |
phoneNumber | string | Numero di telefono del destinatario |
status | string | Stato del messaggio (vedi sotto) |
timestamp | ISO 8601 | Quando lo stato è cambiato |
channel | string | Canale utilizzato (sms, rcs, whatsapp) |
error | string o null | Messaggio di errore se lo stato è ERROR |
Valori di Stato
| Stato | Significato |
|---|---|
SENT | Il messaggio ha lasciato i nostri server ed è in elaborazione |
DELIVERED | Consegnato con successo al destinatario |
ERROR | La consegna è fallita (vedi campo di errore) |
EXPIRED | Il messaggio non è stato consegnato entro il periodo di conservazione |
Implementare un Ricevitore Webhook
Il tuo endpoint deve:
- Ascoltare richieste POST
- Restituire HTTP 200 entro 5 secondi
- Elaborare il payload in modo asincrono (non bloccare la risposta)
- Essere idempotente (gestire gli eventi duplicati correttamente)
- Validare che la richiesta provenga da Qlara
Esempio di Ricevitore Webhook
- Python (Flask)
- Node.js (Express)
- PHP (Laravel)
from flask import Flask, request, jsonify
import json
app = Flask(__name__)
@app.route('/webhook', methods=['POST'])
def webhook():
# Restituisci 200 immediatamente
payload = request.get_json()
# Elabora in modo asincrono (metti in coda a un worker in background)
process_delivery_event.delay(payload)
return jsonify({"status": "received"}), 200
def process_delivery_event(payload):
"""Elabora il payload webhook in modo asincrono"""
message_id = payload.get('messageId')
status = payload.get('status')
phone = payload.get('phoneNumber')
# Aggiorna il tuo database
Message.objects(messageId=message_id).update(
status=status,
delivered_at=payload.get('timestamp')
)
# Attiva azioni downstream
if status == 'DELIVERED':
send_confirmation_email(phone)
elif status == 'ERROR':
log_delivery_error(message_id, payload.get('error'))
if __name__ == '__main__':
app.run(port=5000)
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhook', async (req, res) => {
// Restituisci 200 immediatamente
res.status(200).json({ status: 'received' });
// Elabora in modo asincrono
const payload = req.body;
processDeliveryEvent(payload).catch(err => console.error(err));
});
async function processDeliveryEvent(payload) {
const { messageId, status, phoneNumber, timestamp, error } = payload;
// Aggiorna il tuo database
await Message.updateOne(
{ messageId },
{
status,
deliveredAt: timestamp
}
);
// Attiva azioni downstream
if (status === 'DELIVERED') {
await sendConfirmationEmail(phoneNumber);
} else if (status === 'ERROR') {
await logDeliveryError(messageId, error);
}
}
app.listen(5000, () => console.log('Webhook receiver running on port 5000'));
Route::post('/webhook', function (Request $request) {
// Restituisci 200 immediatamente
$payload = $request->json()->all();
// Metti in coda per elaborazione asincrona
dispatch(new ProcessDeliveryEvent($payload))->onQueue('webhooks');
return response()->json(['status' => 'received'], 200);
});
class ProcessDeliveryEvent implements ShouldQueue {
public function handle() {
$payload = $this->payload;
$messageId = $payload['messageId'];
$status = $payload['status'];
// Aggiorna database
Message::where('message_id', $messageId)
->update(['status' => $status]);
// Attiva azioni
if ($status === 'DELIVERED') {
Mail::send('confirmation', [], function ($msg) {
// invia email di conferma
});
}
}
}
Requisiti del Ricevitore Webhook
HTTPS Richiesto
L'URL del tuo webhook deve utilizzare HTTPS. HTTP non crittografato non è supportato.
Risposta Veloce
Restituisci HTTP 200 entro 5 secondi. Non elaborare il payload del webhook prima di rispondere.
# BENE: Restituisci immediatamente, elabora dopo
@app.route('/webhook', methods=['POST'])
def webhook():
queue.enqueue(process_event, request.get_json())
return jsonify({"status": "received"}), 200
# MALE: Blocca prima di restituire
@app.route('/webhook', methods=['POST'])
def webhook():
process_event(request.get_json()) # Impiega 10 secondi
return jsonify({"status": "processed"}), 200
Idempotenza
Elabora gli eventi in modo idempotente. Potresti ricevere lo stesso evento due volte. Usa il messageId per deduplicare:
def process_delivery_event(payload):
message_id = payload['messageId']
# Controlla se già elaborato
if Message.objects(messageId=message_id).first():
return # Salta il duplicato
# Elabora l'evento
Message.create(messageId=message_id, status=payload['status'])
Alternativa di Polling
Se non puoi usare i webhook, fai polling dell'endpoint di stato:
Endpoint: GET /messages/status/{customerMessageId}
- cURL
- Python
- Node.js
curl -X GET https://lora-api.agiletelecom.com/api/messages/status/your_reference_id \
-H "X-Api-Key: your_api_key_here"
import requests
import time
api_key = "your_api_key_here"
customer_message_id = "your_reference_id"
url = f"https://lora-api.agiletelecom.com/api/messages/status/{customer_message_id}"
headers = {"X-Api-Key": api_key}
# Fai polling ogni 5 secondi fino alla consegna
max_attempts = 60
attempts = 0
while attempts < max_attempts:
response = requests.get(url, headers=headers)
data = response.json()
status = data.get('status')
if status in ['DELIVERED', 'ERROR', 'EXPIRED']:
print(f"Final status: {status}")
break
attempts += 1
time.sleep(5)
const axios = require('axios');
const apiKey = 'your_api_key_here';
const customerMessageId = 'your_reference_id';
const url = `https://lora-api.agiletelecom.com/api/messages/status/${customerMessageId}`;
async function pollStatus() {
const headers = { 'X-Api-Key': apiKey };
let attempts = 0;
while (attempts < 60) {
const response = await axios.get(url, { headers });
const status = response.data.status;
if (['DELIVERED', 'ERROR', 'EXPIRED'].includes(status)) {
console.log(`Final status: ${status}`);
break;
}
attempts++;
await new Promise(resolve => setTimeout(resolve, 5000));
}
}
pollStatus();
Best Practice
- Usa webhook in produzione — Più veloci e affidabili del polling
- Restituisci 200 immediatamente — Non bloccare la risposta del webhook
- Elabora in modo asincrono — Usa una job queue (Celery, Bull, RQ, ecc.)
- Gestisci i duplicati — Implementa l'elaborazione idempotente
- Usa HTTPS — I webhook devono essere crittografati
- Registra tutti gli eventi — Mantieni un audit trail dello stato di consegna
- Monitora la salute dei webhook — Avvisa se l'URL del webhook è irraggiungibile
- Valida le richieste — (Future: firme webhook in arrivo)
Risoluzione dei Problemi
Il webhook non viene chiamato?
- Verifica che l'endpoint sia accessibile da internet
- Controlla la validità del certificato HTTPS
- Assicurati che il firewall consenta richieste dagli IP Qlara
- Controlla i log dell'applicazione per errori
Ricevi eventi duplicati?
- Implementa l'elaborazione idempotente con deduplicazione di messageId
- Usa vincoli di unicità del database come rete di sicurezza
Il webhook restituisce errori?
- Restituisci HTTP 200 per tutti i payload validi
- Registra gli errori e ri-lancia all'interno di job asincroni, non nel handler del webhook
- Verifica che l'endpoint restituisca entro 5 secondi
Hai bisogno di aiuto? Contatta support@agiletelecom.com.