urmi-ai/backend/app/sms_webhooks.py
2026-10-02 11:22:40 +05:30

68 lines
2.2 KiB
Python

import asyncio
import json
import logging
import uuid
from datetime import datetime, timezone
import httpx
from .endpoint_security import payload_signature
from .models import SmsDeliveryWebhook
logger = logging.getLogger(__name__)
def _event_body(data: dict) -> bytes:
payload = {
"id": f"evt_{uuid.uuid4().hex}",
"event": "message.delivery.updated",
"created_at": datetime.now(timezone.utc).isoformat(),
"data": data,
}
return json.dumps(payload, separators=(",", ":"), ensure_ascii=False).encode()
async def deliver_report(
endpoint: SmsDeliveryWebhook,
data: dict,
*,
attempts: int = 3,
) -> int:
body = _event_body(data)
headers = {
"Content-Type": "application/json",
"User-Agent": "Urmi-Webhooks/1.0",
"X-Urmi-Event": "message.delivery.updated",
"X-Urmi-Endpoint-Id": str(endpoint.id),
"X-Urmi-Signature": payload_signature("webhook", endpoint.id, body),
}
last_error: Exception | None = None
async with httpx.AsyncClient(timeout=10.0, follow_redirects=False) as client:
for attempt in range(attempts):
try:
response = await client.post(endpoint.url, content=body, headers=headers)
response.raise_for_status()
return response.status_code
except (httpx.HTTPError, httpx.TimeoutException) as exc:
last_error = exc
if attempt + 1 < attempts:
await asyncio.sleep(2**attempt)
logger.warning("SMS delivery webhook failed endpoint=%s: %s", endpoint.id, last_error)
raise RuntimeError("Webhook endpoint did not return a successful response") from last_error
async def deliver_test_report(endpoint: SmsDeliveryWebhook) -> int:
if not endpoint.enabled:
raise RuntimeError("Webhook is disabled")
return await deliver_report(
endpoint,
{
"message_id": f"msg_test_{uuid.uuid4().hex[:16]}",
"to": "+919999999999",
"from": endpoint.phone_number.phone_number if endpoint.phone_number else "test",
"status": "delivered",
"updated_at": datetime.now(timezone.utc).isoformat(),
"test": True,
},
attempts=1,
)