Inbound routing

Decide, per call, which agent answers a phone number, or turn the call away, by answering a signed request from Dialgem.

By default a phone number is answered by the agent you configured for it. With an inbound routing webhook, Dialgem asks your server first, as the call arrives and before it is answered, and your answer can:

  • pick a different agent (and a version of it) for this call,
  • add dynamic variables that fill {{placeholders}} in the agent's prompt,
  • attach metadata to the call record,
  • or reject the call.

Typical uses: route a known customer to their account manager's agent, greet a caller by name, send after-hours calls to a different agent, or turn away numbers you have blocked.

The number still needs an inbound agent configured. That agent answers whenever your server does not say otherwise, and it is what keeps a call from being lost when your server is slow or down.

Set it up

In the dashboard: Phone numbers, choose the number, Inbound call webhook. Or with the API:

bash
curl -X PATCH https://api.dialgem.com/v1/phone-numbers/PHONE_NUMBER_ID \
  -H "X-API-Key: $DIALGEM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "inbound_webhook_url": "https://example.com/dialgem/route" }'

The response includes the signing secret (inbound_webhook_secret) once. Afterwards only the last four characters are shown (inbound_webhook_secret_hint). Rotate it with POST /v1/phone-numbers/{id}/inbound-webhook/rotate-secret; the old secret stops working at once. Send null as the URL to remove the webhook.

The URL must be https and reachable from the internet. Addresses on private networks are refused.

Send a test (dashboard button, or POST /v1/phone-numbers/{id}/inbound-webhook/test) makes one signed request with "test": true, then reports the status, how long you took against the time budget, and what a real call would have done with your answer, including any field Dialgem ignored and why.

The request

POST with a JSON body:

json
{
  "event": "inbound_call",
  "call_id": "6f1d0c2e-0b8a-4c7e-9d58-2a1e3f4b5c6d",
  "call_sid": "CA0123456789abcdef",
  "direction": "inbound",
  "from_number": "+14155550123",
  "to_number": "+14155550199",
  "agent_id": "the number's configured agent"
}

call_id is the id the call will have in your call list, so you can match it to the call record and to later webhook events. A carrier that retries its own webhook gets the same decision and your server is not asked again.

The request is signed like webhooks: the X-Dialgem-Signature header is v=<timestamp_ms>,d=<hex> where hex = HMAC-SHA256(secret, raw_body + timestamp_ms). Verify it against the raw bytes, and reject anything you did not expect, since this endpoint decides who answers your calls.

Your answer

Reply 200 with a JSON object. Every field is optional, and an empty body or {} means "carry on as configured".

json
{
  "agent_id": "AGENT_ID",
  "agent_version": 3,
  "dynamic_variables": { "caller_name": "Ada", "plan": "pro" },
  "metadata": { "crm_id": "c-42" }
}
FieldEffect
agent_idThis agent answers instead. It must belong to your organization and not be deleted.
agent_versionA published version of that agent. An unknown version voids agent_id (the configured agent answers), but variables and metadata still apply.
dynamic_variablesFills {{name}} in the agent's prompt. Names use letters, digits and _ - . / (up to 64 characters), at most 50 variables, values up to 500 characters. Others are ignored.
metadataStored on the call record. Up to 16 KB.
rejecttrue, "busy" or "rejected" turns the call away; the carrier is told busy or rejected accordingly. Nothing else in the answer is used. Any non-empty string rejects, so do not send "false".

A rejected call appears in your call list as failed with the disconnection reason inbound_rejected, so you can see what you turned away.

Dialgem does not keep your answer. The call record keeps only what happened: the outcome, how long you took, and which fields were ignored.

The time limit, and what happens when you miss it

The caller hears nothing until the call is answered, so Dialgem cannot wait for you.

  • You have 1.5 seconds in total, including connecting. That is the whole budget, retry included.
  • A failure that could be momentary (timeout, connection error, 5xx, 408, 425, 429) is retried once if enough time is left. A 4xx or an unreadable answer is not retried.
  • If anything goes wrong, the call is answered by the number's configured agent. A slow, down, erroring or nonsensical endpoint never drops a call. Only a well-formed 2xx answer containing reject turns a call away.
  • Redirects are not followed.
  • Answers over 64 KB are treated as unreadable.

The circuit breaker

An endpoint that keeps being slow would cost every caller up to 1.5 seconds. So after 5 failures that cost time (timeouts, connection errors, 5xx) within a minute, Dialgem stops asking that URL for 30 seconds, then tries one call again. If that fails the pause doubles, up to 5 minutes. A success closes it. While it is open, calls are answered immediately by the configured agent. 4xx answers and unreadable answers do not trip it, because they cost no time.

Not covered

  • SIP trunk numbers. A call arriving on a SIP trunk is routed by the trunk's own configuration, so the webhook is not asked.
  • Outbound calls. Their agent is chosen when you place them.