68 lines
2.2 KiB
Python
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,
|
|
)
|