Skip to main content

Webhooks

Receive real-time notifications when message status changes. Webhooks are faster and more reliable than polling.

Webhooks vs Polling

MethodSpeedReliabilityUse Case
WebhooksInstantHigh (your endpoint receives events)Production systems, real-time processing
PollingDelayed (you check every N seconds)Depends on frequencyLow-volume, simple integrations

Recommendation: Use webhooks for production.

Webhook Setup

Register a Webhook Endpoint

You can manage webhooks via the API or dashboard.

API Endpoint: POST /webhooks/delivery-status

Register your webhook:

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"
}'

Important Limitation

One active webhook per account. If you register a new webhook, the previous one is replaced.

Webhook Payload

When a message status changes, Qlara sends a POST request to your endpoint:

{
"messageId": "msg_1234567890",
"customerMessageId": "your_reference_id",
"phoneNumber": "+393901234567",
"status": "DELIVERED",
"timestamp": "2025-04-08T14:30:00Z",
"channel": "sms",
"error": null
}

Payload Fields

FieldTypeDescription
messageIdstringUnique Qlara message ID
customerMessageIdstringYour reference ID (if provided)
phoneNumberstringRecipient phone number
statusstringMessage status (see below)
timestampISO 8601When the status changed
channelstringChannel used (sms, rcs, whatsapp)
errorstring or nullError message if status is ERROR

Status Values

StatusMeaning
SENTMessage left our servers and is being processed
DELIVEREDSuccessfully delivered to recipient
ERRORDelivery failed (see error field)
EXPIREDMessage was not delivered within retention period

Implementing a Webhook Receiver

Your endpoint must:

  1. Listen for POST requests
  2. Return HTTP 200 within 5 seconds
  3. Process the payload asynchronously (don't block the response)
  4. Be idempotent (handle duplicate events gracefully)
  5. Validate the request came from Qlara

Example Webhook Receiver

from flask import Flask, request, jsonify
import json

app = Flask(__name__)

@app.route('/webhook', methods=['POST'])
def webhook():
# Return 200 immediately
payload = request.get_json()

# Process asynchronously (queue to background worker)
process_delivery_event.delay(payload)

return jsonify({"status": "received"}), 200

def process_delivery_event(payload):
"""Process webhook payload asynchronously"""
message_id = payload.get('messageId')
status = payload.get('status')
phone = payload.get('phoneNumber')

# Update your database
Message.objects(messageId=message_id).update(
status=status,
delivered_at=payload.get('timestamp')
)

# Trigger downstream actions
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)

Webhook Receiver Requirements

HTTPS Required

Your webhook URL must use HTTPS. Unencrypted HTTP is not supported.

Fast Response

Return HTTP 200 within 5 seconds. Don't process the webhook payload before responding.

# GOOD: Return immediately, process later
@app.route('/webhook', methods=['POST'])
def webhook():
queue.enqueue(process_event, request.get_json())
return jsonify({"status": "received"}), 200

# BAD: Blocks before returning
@app.route('/webhook', methods=['POST'])
def webhook():
process_event(request.get_json()) # Takes 10 seconds
return jsonify({"status": "processed"}), 200

Idempotency

Process events idempotently. You may receive the same event twice. Use the messageId to deduplicate:

def process_delivery_event(payload):
message_id = payload['messageId']

# Check if already processed
if Message.objects(messageId=message_id).first():
return # Skip duplicate

# Process event
Message.create(messageId=message_id, status=payload['status'])

Polling Alternative

If you can't use webhooks, poll the status endpoint:

Endpoint: GET /messages/status/{customerMessageId}

curl -X GET https://lora-api.agiletelecom.com/api/messages/status/your_reference_id \
-H "X-Api-Key: your_api_key_here"

Best Practices

  1. Use webhooks in production — Faster and more reliable than polling
  2. Return 200 immediately — Don't block the webhook response
  3. Process asynchronously — Use a job queue (Celery, Bull, RQ, etc.)
  4. Handle duplicates — Implement idempotent processing
  5. Use HTTPS — Webhooks must be encrypted
  6. Log all events — Keep audit trail of delivery status
  7. Monitor webhook health — Alert if webhook URL is unreachable
  8. Validate requests — (Future: webhook signatures coming soon)

Troubleshooting

Webhook not being called?

  • Verify endpoint is accessible from the internet
  • Check HTTPS certificate validity
  • Ensure firewall allows requests from Qlara IPs
  • Check application logs for errors

Getting duplicate events?

  • Implement idempotent processing with messageId deduplication
  • Use database unique constraints as a safety net

Webhook returning errors?

  • Return HTTP 200 for all valid payloads
  • Log errors and re-throw within async job, not in webhook handler
  • Check that endpoint returns within 5 seconds

Need help? Contact support@agiletelecom.com.