Developers
Onyx Phone API and webhooks
Read calls, voicemails, and texts, send a text from a line, and get a webhook the moment a call ends or a text comes in. It is the same API our Zapier app uses.
Authentication
An account admin makes a key in Onyx Phone under Account → API and Zapier. The key is shown once. Send it on every request in the X-API-Key header:
curl https://www.onyxphone.io/api/v1/me \ -H "X-API-Key: onyx_…"
A missing, wrong, or revoked key gets 401. Requests are limited to 300 a minute per key; past that you get 429. Errors are JSON: { "error": "…" }. Every time is ISO 8601 in UTC, and phone numbers are E.164 (+16125550199), each with _national and _formatted versions beside it.
Endpoints
Base URL: https://www.onyxphone.io/api/v1
| Request | What it does |
|---|---|
GET /me | The account the key belongs to: id, account_name, plan. |
GET /lines | The account's lines: id, name, number. |
GET /calls | Recent calls, newest first. event picks which: call.completed (default), call.missed, or voicemail.received. Optional line_id, and limit (1–100, default 25). |
GET /calls/:id | One call. |
GET /messages | Recent texts, newest first. Optional direction (inbound or outbound), line_id, limit. |
POST /messages | Sends a text: line_id, to, body (up to 1,600 characters). Needs Texting on the plan and carrier registration approved; numbers that replied STOP are refused. Returns 201 with the text. |
GET /webhooks | The account's webhook subscriptions. |
POST /webhooks | Subscribes: event, target_url (https, public internet), optional line_id. Returns 201 with the subscription's id and its signing secret. |
DELETE /webhooks/:id | Unsubscribes. Succeeds even if it was already removed. |
Webhook events
| Event | Sent when |
|---|---|
call.completed | A call ended, incoming or outgoing, answered or not. |
call.missed | An incoming call ended without anyone answering. |
voicemail.received | A caller left a voicemail (sent once the transcript is ready). |
message.received | A text came in. |
message.sent | A text went out. |
Each event is POSTed as one JSON object, the same shape GET /calls and GET /messages return. Each call or text is sent once per subscription. Answer with any 2xx. Failed deliveries are retried up to five times with growing gaps; answer 410 Gone to unsubscribe for good. A target that fails 25 deliveries in a row is removed.
A call event
{
"id": "4821",
"event": "call.missed",
"occurred_at": "2026-10-23T15:04:11Z",
"occurred_on": "2026-10-23",
"direction": "inbound",
"direction_label": "Inbound",
"outcome": "voicemail",
"outcome_label": "Voicemail",
"summary": "Voicemail from (612) 555-0199 on Main line",
"contact_number": "+16125550199",
"contact_number_national": "6125550199",
"contact_number_formatted": "(612) 555-0199",
"caller_name": null,
"line_id": "12",
"line_name": "Main line",
"line_number": "+16125550100",
"started_at": "2026-10-23T15:03:20Z",
"ended_at": "2026-10-23T15:04:11Z",
"duration_seconds": 51,
"duration_ms": 51000,
"answered": false,
"answered_at": null,
"answered_by_name": null,
"answered_by_email": null,
"voicemail": true,
"voicemail_seconds": 22,
"voicemail_transcript": "Hi, this is Sam about the water heater…",
"notes": null,
"hubspot_call_direction": "INBOUND",
"hubspot_call_status": "NO_ANSWER",
"app_url": "https://www.onyxphone.io/call_logs/4821"
}
Abbreviated: from_number, to_number, and line_number also come with _national and _formatted versions. outcome is answered, voicemail, or missed for incoming calls, and completed, no_answer, busy, failed, or canceled for outgoing ones. Keys are only ever added, never renamed or removed.
Checking the signature
Every delivery carries X-Onyx-Signature: t=<unix time>,v1=<signature>. The signature is the hex HMAC-SHA256 of <t>.<raw body>, keyed with the subscription's secret. Compare it in constant time, and reject times more than five minutes old.
expected = OpenSSL::HMAC.hexdigest("SHA256", secret, "#{t}.#{raw_body}")
ActiveSupport::SecurityUtils.secure_compare(expected, v1)
MCP server for AI assistants
Onyx Phone runs a Model Context Protocol server at POST /mcp (Streamable HTTP, JSON responses). It supports OAuth 2.1 sign-in, as the MCP authorization spec describes: discovery at /.well-known/oauth-protected-resource and /.well-known/oauth-authorization-server, dynamic client registration, and the authorization code flow with PKCE (S256). Tools you build yourself can send an API key instead, as Authorization: Bearer onyx_…. Tools: list_lines, list_calls, get_call, list_messages, send_text, and add_call_note. Each returns the same JSON as the API above, with call transcripts and summaries where a line records calls. Texts sent with send_text are billed like texts sent through the API.
Zapier
The Onyx Phone Zapier app has triggers for New Call, New Missed Call, New Voicemail, and New Text, and a Send Text action. Ready-made Zaps send missed calls and voicemails to Jobber as requests, and log calls in HubSpot and Salesforce. See integrations.