Telephony Debugging
Admin-only debug endpoints for triaging telephony issues — call inspector, credentials probe, and inbound webhook simulator.
Telephony Debugging
Voisnap.Admin.API exposes three operator-facing endpoints under /api/v1/telephony/debug for triaging telephony issues without bouncing through Cloud Logging. All require an admin bearer token (Authorize(Policy = "AdminOnly")).
The simulator additionally requires the Development environment — it returns 403 in staging/production to keep webhook simulation out of the production attack surface.
| Endpoint | Method | Purpose |
|---|---|---|
/api/v1/telephony/debug/calls/{callSid} | GET | Call inspector — joins Conversation + OutboundCall by provider id |
/api/v1/telephony/debug/validate-connection | POST | Credentials probe against a provider |
/api/v1/telephony/debug/simulate-inbound | POST | Dev-only TeXML/TwiML/NCCO preview |
/api/v1/telephony/debug/repair-provider-sids | POST | Telnyx: rewrite stored ProviderSid to the canonical phone_number.id |
1. Call Inspector — GET calls/{callSid}
Use when: "The customer says they called but I can't find anything," or you need to see status/outcome/cost for a specific call without writing SQL.
Takes the provider-issued id (Twilio CallSid, Telnyx call_control_id, Vonage uuid) and returns whatever the database knows about it, joined from both Conversations.ExternalSessionId and OutboundCalls.CallSid.
Request
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
https://admin.api.voisnap.ai/api/v1/telephony/debug/calls/CA1234567890abcdef
Response (match)
{
"callSid": "CA1234567890abcdef",
"conversation": {
"id": "…",
"tenantId": "…",
"agentId": "…",
"channel": "Voice",
"status": "Completed",
"startedAt": "2026-05-28T12:01:00Z",
"endedAt": "2026-05-28T12:03:42Z",
"durationSeconds": 162,
"callerIdentifier": "+14155551234",
"outcome": "Resolved",
"costTotal": 0.0834,
"messageCount": 24
},
"outboundCall": null
}
For outbound calls, outboundCall is populated (with to, from, status, provider, retryCount, error, timestamps) and conversation may be null if the leg never connected.
Response (no match)
404 with a triage hint:
{
"callSid": "CA1234567890abcdef",
"message": "No Conversation or OutboundCall found for this id. The webhook may not have reached the API, the agent may not have been resolved, or the call may belong to a different region. Check Cloud Logging filtered by CallSid."
}
When this happens, in order:
- Confirm the region — the call may live in
mewest.admin.api.voisnap.ainot the NY admin. - In Cloud Logging, filter on the
CallSidto see whether the webhook even arrived. - Check
agentId/tenantIdresolution — calls that don't map to a known phone number get dropped before persistence.
2. Credentials Probe — POST validate-connection
Use when: A new provider's keys were just added, or you suspect a 401 storm. Makes a low-cost authenticated call (a pricing lookup) so credential failure surfaces immediately instead of when the next real call comes in.
Request
curl -X POST \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"provider": "Telnyx", "countryCode": "US"}' \
https://admin.api.voisnap.ai/api/v1/telephony/debug/validate-connection
provider accepts Twilio, Vonage, Telnyx. countryCode defaults to US.
Response (success)
{
"provider": "Telnyx",
"success": true,
"probeCountry": "US",
"detail": "Authenticated probe returned pricing for US: localMonthlyCost=1.00"
}
Response (failure)
Returns 200 with success: false — credentials probing is itself a debug operation, not an error.
{
"provider": "Vonage",
"success": false,
"detail": "Vonage:ApiKey is not configured"
}
Common failure causes by message:
| Detail | Cause |
|---|---|
<Provider>:ApiKey is not configured | Cloud Run secret missing or not bound to the service |
Unauthorized / 401 | Wrong key/secret pair, or revoked |
Forbidden / 403 | Key exists but lacks the required scope (e.g. messaging-only) |
| HTTP timeout | Network egress blocked, or provider regional outage |
3. Inbound Simulator — POST simulate-inbound (dev only)
Use when: Adding a new provider, changing TeXML/TwiML/NCCO generation, or debugging "the call connects but no media flows." Returns exactly the response body the platform would emit, with no webhook signature, no DB writes, no side effects.
Returns 403 Forbidden outside the Development environment.
Request
curl -X POST \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"provider": "Twilio",
"agentId": "11111111-1111-1111-1111-111111111111",
"tenantId": "22222222-2222-2222-2222-222222222222",
"from": "+14155551234",
"to": "+18005550000",
"host": "localhost:5001"
}' \
http://localhost:5001/api/v1/telephony/debug/simulate-inbound
Optional: callSid (defaults to SIMULATED-<guid>), isSipTrunk (defaults to false).
Response
{
"provider": "Twilio",
"mediaStreamUrl": "wss://localhost:5001/api/v1/webhooks/twilio/media-stream?agentId=…&tenantId=…",
"responseBody": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Response>\n <Connect>\n <Stream url=\"…\">…</Stream>\n </Connect>\n</Response>"
}
The responseBody is the exact TwiML / NCCO / TeXML the provider would receive on a real inbound webhook. Inspect it for:
- Correct media-stream URL — wrong host or scheme means the carrier will never connect the WebSocket.
- Custom parameters / headers —
agentId,tenantId,callerNumber, etc., must arrive in the WebSocket "start" event for the runtime to bind the call to an agent. - Connection type marker — for SIP trunks, the
connectionType=sipparameter (Twilio) or equivalent must be present, otherwise the channel adapter falls back to PSTN audio codec settings.
4. Repair Telnyx ProviderSids — POST repair-provider-sids
Use when: assigning a Telnyx number to an agent logs Failed to update provider webhook with a Telnyx 404 Resource not found on PATCH /v2/phone_numbers/{id}. The stored ProviderSid is no longer (or never was) a real phone_number.id under the current API key — typically because the number was provisioned by an older code path that stored the number_order item id instead of the phone_number resource id.
For every active Telnyx number in the pool, this endpoint:
- Calls
GET /v2/phone_numbers?filter[phone_number]=<E.164>to find Telnyx's canonical id. - If it differs from the stored
ProviderSid, rewrites the DB row. - Reports per-number status (
repaired,already-correct,missing-on-telnyx,error: …).
Request
# Dry run first — shows what *would* change without writing
curl -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \
"https://admin.api.voisnap.ai/api/v1/telephony/debug/repair-provider-sids?dryRun=true"
# Live repair
curl -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \
https://admin.api.voisnap.ai/api/v1/telephony/debug/repair-provider-sids
Response
{
"dryRun": false,
"scanned": 3,
"repaired": 1,
"alreadyCorrect": 2,
"missingOnProvider": 0,
"errors": 0,
"items": [
{
"id": "deef0342-…",
"phoneNumber": "+97283761547",
"storedProviderSid": "de27c407-…",
"canonicalProviderSid": "1234567890123456789",
"action": "repaired"
}
]
}
missing-on-telnyx means the number couldn't be found under the current API key — investigate whether the number was released, or whether the API key points at a different Telnyx account than the one that owns the number.
After running repair, re-assign the affected number to the agent: the webhook PATCH will now hit the correct phone_number.id and succeed.
Triage flow
A typical inbound-call outage walk-through:
validate-connection— rule out credential drift first.calls/{callSid}— does the platform even know about the call? If404, the webhook never landed; check provider dashboards + Cloud Logging.- If the call landed but media never flowed:
simulate-inboundin dev with the same agent/tenant and compareresponseBodyagainst what the provider received in their webhook logs.
For SMS or outbound issues, validate-connection covers credentials but the inspector is your fastest signal — outboundCall.error and retryCount typically tell the story.
Related telemetry
These endpoints complement, not replace, the OpenTelemetry counters emitted from the providers themselves:
| Counter | When it increments |
|---|---|
voisnap.telephony.api_errors | Provider REST call failed (tags: provider, method) — covers place-call, hangup, etc. |
voisnap.telephony.media_stream_closed | Media-stream WebSocket closed (tag: reason) |
When api_errors spikes for a single provider, validate-connection is the first thing to run.