Dynamic Routing Webhook
Dynamic routing lets your server decide, in real time, who a masked number connects to on each call — instead of pre-registering a fixed mapping. Point a number at your HTTPS endpoint; when a call arrives, we ask your endpoint for the target and bridge the call to whatever it returns (or reject it).
This is ideal for round-robin agent assignment, time- or location-based routing, and blocking abusive callers — any decision your own systems already know how to make.
Enabling it
Set a number's routing mode to webhook and give it your endpoint. See Enroll Your Own Numbers for the PATCH /masking/numbers/:id/routing call, including webhook_url, webhook_method, webhook_secret, and fallback_internal.
The request we send
When a call reaches a webhook-mode number, we POST a JSON body to your endpoint:
{
"event": "masking.route",
"call_sid": "d28be7cd-9840-4b23-9627-199f1ef81fc6",
"caller": "9876543210",
"masked_number": "918071387146",
"direction": "inbound",
"timestamp": "2026-07-14T18:00:00Z"
}
| Field | Description |
|---|---|
event |
Always masking.route. |
call_sid |
The call's id (empty for test events). |
caller |
The normalized number of the person calling. |
masked_number |
The virtual number that was dialed. |
direction |
inbound. |
timestamp |
RFC 3339 UTC. |
Signature verification
If you set a webhook_secret, we sign the raw request body with HMAC-SHA256 and send it in the X-Edesy-Signature header, formatted as sha256=<hex>. Recompute the HMAC over the exact body you received and compare to confirm the request genuinely came from us.
Limits
- Timeout: your endpoint must respond within 5 seconds — the caller is on the line while you decide.
- Response size: we read at most 8 KB of your response.
- Method:
POST(default) orGET.
The response we expect
Reply with JSON telling us what to do:
{
"action": "connect",
"target_number": "9123456789",
"caller_id": "918071387146"
}
| Field | Type | Description |
|---|---|---|
action |
string | connect (default when a target_number is present) or reject. |
target_number |
string | Number to bridge to. Required when action is connect. |
caller_id |
string | Optional caller-ID override. Defaults to the masked number. |
reject_reason |
string | Optional message played to the caller when action is reject. |
To turn a call away, return:
{ "action": "reject", "reject_reason": "This line is closed. Please try again during business hours." }
Fallback
If your endpoint errors, times out, or returns a non-2xx status, behaviour depends on the number's fallback_internal setting: when enabled, we fall back to the number's internal mapping; when disabled, the call is rejected. Set this based on how critical it is that calls always connect.
Testing
Use POST /masking/numbers/:id/routing/test to send a synthetic masking.route event to your endpoint and see the HTTP status, latency, raw body, and the decision we parsed — so you can validate your integration before going live.
Related
- Enroll Your Own Numbers
- Event Webhooks — for lifecycle notifications (distinct from routing)