Phone Webhook API Reference
Connect any phone system to Closer Mode using a single POST request. Supports Twilio, Salesforce, 360CTI, or any custom dialer — with optional inline transcripts and metadata passthrough.
Endpoint
Each integration gets a unique webhook URL generated at setup time. The URL contains a per-integration token that acts as the first layer of authentication.
POST https://app.closermode.ai/api/webhooks/phone/generic/{token}
Content-Type: application/json
Authorization: Bearer {api_key}Authentication
Send your API key as a Bearer token in the Authorization header:
Authorization: Bearer cm_live_a1b2c3d4e5f6...The key is verified using a constant-time SHA-256 comparison — plaintext is never stored. If your key is compromised, regenerate it from the dashboard.
Security Layers
The webhook URL contains a unique UUID token per integration. Guessing it is computationally infeasible.
SHA-256 hashed, verified with constant-time comparison to prevent timing attacks. Shown once at creation.
Restrict inbound requests to specific IP ranges in your integration settings.
Request Body
Send a JSON payload. Only externalId is required — all other fields are optional but improve scoring quality.
| Field | Type | Status | Description |
|---|---|---|---|
externalId | string | required | Your system's unique call ID. Used for idempotency — re-sending the same externalId is safe and returns the existing call. |
repName | string | optional | Display name of the sales rep. Used in call titles and participant lists. |
callerPhone | string | optional | Caller's phone number in E.164 format (e.g. +15551234567). |
duration | number | optional | Call duration in seconds. |
callDate | string | optional | ISO 8601 datetime of when the call occurred. Defaults to current time if omitted. |
recordingUrl | string (url) | optional | Direct URL to the audio recording. Closer Mode will download and transcribe it if no transcript is provided. |
transcript | string | optional | Pre-built transcript text. When provided, audio transcription is skipped — fastest path to scoring. |
metadata | object | optional | Arbitrary key-value pairs stored alongside the call. Useful for CRM IDs, lead IDs, campaign tags, etc. |
Call Filtering
To avoid scoring incomplete or irrelevant calls, Closer Mode applies these rules before creating a call record:
Filtered calls receive { "skipped": true, "reason": "..." } with HTTP 200. Your phone system can safely ignore these.
Recording URLs
Twilio, smrtPhone, and most dialers provide public recording URLs. Closer Mode fetches these directly — no extra config needed.
https://api.twilio.com/recordings/RE...Salesforce-hosted recordings require OAuth and cannot be fetched directly. Send a transcript instead to bypass this limitation.
Pass transcript field directly ↓Inline Transcripts
The fastest path to scoring: include the transcript directly in the payload. Closer Mode skips transcription entirely and goes straight to AI scoring.
Transcript strategy (in order of preference)
transcriptfield provided → use directly, skip audio downloadrecordingUrlprovided → download audio and transcribe via AssemblyAI- Neither provided → call is skipped (no recording)
Transcript format: plain text, speaker turns separated by newlines. Label speakers as Agent: / Customer: for best scoring accuracy.
Metadata Passthrough
The metadata object is stored verbatim alongside the call and is available in exports and downstream webhooks. Use it to attach CRM record IDs, campaign tags, or any other context your team needs.
{
"externalId": "call-abc123",
"metadata": {
"leadId": "lead-456",
"callLogId": "log-789",
"campaignId": "spring-2026",
"source": "360CTI",
"disposition": "Interested"
}
}Examples
Minimal payload
curl -X POST \
"https://app.closermode.ai/api/webhooks/phone/generic/{token}" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer {api_key}" \
-d '{
"externalId": "call-20260424-001",
"recordingUrl": "https://example.com/recordings/call-001.mp3"
}'Full payload with transcript
curl -X POST \
"https://app.closermode.ai/api/webhooks/phone/generic/{token}" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer {api_key}" \
-d '{
"externalId": "call-20260424-002",
"repName": "Jane Smith",
"callerPhone": "+15551234567",
"duration": 245,
"callDate": "2026-04-24T10:00:00Z",
"transcript": "Agent: Hi, this is Jane. How can I help you?\nCustomer: I saw your listing...",
"metadata": {
"leadId": "lead-999",
"callLogId": "log-20260424",
"source": "360CTI"
}
}'Node.js
const response = await fetch(
"https://app.closermode.ai/api/webhooks/phone/generic/{token}",
{
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer {api_key}",
},
body: JSON.stringify({
externalId: call.id,
repName: call.agentName,
callerPhone: call.fromNumber,
duration: call.durationSeconds,
callDate: call.startedAt,
recordingUrl: call.recordingUrl,
metadata: {
leadId: call.leadId,
source: "my-dialer",
},
}),
}
);
const result = await response.json();
// { success: true, callId: "uuid..." }Responses
{ "success": true, "callId": "9db7cebd-e628-4415-948c-11ede6187636" }Call was created and queued for AI scoring.
{ "skipped": true, "reason": "duplicate" }Duplicate externalId or no recording/transcript. Safe to ignore.
{ "error": "Unauthorized" }Invalid or missing Bearer token, or wrong webhook URL.
{ "error": "Invalid payload: externalId: Required" }Payload failed schema validation.
Setup Checklist
Ready to connect?
Log in to Closer Mode and add your first Custom integration in under 2 minutes.
Open Integration Settings