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, )