Skip to main content
Webhooks allow you to receive real-time HTTP notifications when call events occur in your application, such as incoming calls, call completions, recordings, and voicemails. For general webhook concepts, see Webhooks.

Creating a Webhook

Subscribe to calling events by creating a webhook for your application. Via Dashboard
  1. Navigate to your application’s Webhooks page
  2. Click “Create Webhook”
  3. Enter your webhook URL
  4. Select the calling events you want to receive
  5. (Optional) Add custom headers for authentication
  6. Save the webhook
Via API Use the POST /v1/webhooks endpoint with calling event types:
Create Calling Webhook
curl -X POST https://api.buildwithchirp.com/v1/webhooks \
  -H "Authorization: Bearer YOUR_APP_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/webhooks/chirp",
    "events": [
      "calls.initiated",
      "calls.answered",
      "calls.completed",
      "calls.failed",
      "calls.recording.completed",
      "calls.voicemail.received"
    ],
    "headers": {
      "X-Webhook-Secret": "your-secret-key"
    }
  }'

Webhook Payload Structure

Every calling webhook includes these top-level fields:
FieldTypeDescription
eventstringThe event type (e.g., calls.completed)
eventIdstringUnique event identifier for idempotency
timestampstringISO 8601 timestamp of when the event was generated
dataobjectEvent-specific data including call and app objects

Call Data

The data.call object is present in all calling webhooks and includes:
FieldTypeDescription
idstringUnique call identifier (e.g., call_2DbBs7GWhGvVNJGrDXr5RG0mBWI)
directionstringinbound or outbound
fromstringPhone number or identifier that initiated the call
tostringPhone number or identifier that received the call
fromChannelstringChannel of the originator: pstn, whatsapp, or webrtc
toChannelstringChannel of the recipient: pstn, whatsapp, or webrtc
livekitRoomNamestringLiveKit room name (present when channel is webrtc)
metadataobjectCustom metadata attached to the call (if any)

Call Lifecycle Events

These events track the progress of a call from initiation to completion.

calls.initiated

Fired when a call is created (outbound) or an incoming call is received (inbound).
calls.initiated payload
{
  "event": "calls.initiated",
  "eventId": "evt_2DbBs7GWhGvVNJGrDXr5RG0mBWI",
  "timestamp": "2024-01-15T10:30:00.000Z",
  "data": {
    "call": {
      "id": "call_2DbBs7GWhGvVNJGrDXr5RG0mBWI",
      "direction": "outbound",
      "from": "+15551234567",
      "to": "+15559876543",
      "fromChannel": "pstn",
      "toChannel": "pstn"
    },
    "app": {
      "id": "app_2DbBs7GWhGvVNJGrDXr5RG0mBWI"
    }
  }
}

calls.ringing

Fired when an outbound call is ringing at the destination.
calls.ringing payload
{
  "event": "calls.ringing",
  "eventId": "evt_3EcCt8HXiHwWOKHsEYs6SH1nCXJ",
  "timestamp": "2024-01-15T10:30:02.000Z",
  "data": {
    "call": {
      "id": "call_2DbBs7GWhGvVNJGrDXr5RG0mBWI",
      "direction": "outbound",
      "from": "+15551234567",
      "to": "+15559876543",
      "fromChannel": "pstn",
      "toChannel": "pstn"
    },
    "app": {
      "id": "app_2DbBs7GWhGvVNJGrDXr5RG0mBWI"
    }
  }
}

calls.answered

Fired when the call is answered. Includes answeredAt and answeredBy fields.
calls.answered payload
{
  "event": "calls.answered",
  "eventId": "evt_4FdDu9IYjIxXPLItFZt7TI2oDYK",
  "timestamp": "2024-01-15T10:30:05.000Z",
  "data": {
    "call": {
      "id": "call_2DbBs7GWhGvVNJGrDXr5RG0mBWI",
      "direction": "outbound",
      "from": "+15551234567",
      "to": "+15559876543",
      "fromChannel": "pstn",
      "toChannel": "pstn",
      "answeredAt": "2024-01-15T10:30:05.000Z",
      "answeredBy": "+15559876543"
    },
    "app": {
      "id": "app_2DbBs7GWhGvVNJGrDXr5RG0mBWI"
    }
  }
}

calls.completed

Fired when a call ends normally. Includes duration, endReason, and endedAt fields.
calls.completed payload
{
  "event": "calls.completed",
  "eventId": "evt_5GeEv0JZkJyYQMJuGAu8UJ3pEZL",
  "timestamp": "2024-01-15T10:32:00.000Z",
  "data": {
    "call": {
      "id": "call_2DbBs7GWhGvVNJGrDXr5RG0mBWI",
      "direction": "outbound",
      "from": "+15551234567",
      "to": "+15559876543",
      "fromChannel": "pstn",
      "toChannel": "pstn",
      "duration": 120,
      "endReason": "hangup",
      "endedAt": "2024-01-15T10:32:00.000Z"
    },
    "app": {
      "id": "app_2DbBs7GWhGvVNJGrDXr5RG0mBWI"
    }
  }
}

calls.failed

Fired when a call fails. Includes an error object with details about the failure.
calls.failed payload
{
  "event": "calls.failed",
  "eventId": "evt_6HfFw1KAlKzZRNKvHBv9VK4qFAM",
  "timestamp": "2024-01-15T10:30:03.000Z",
  "data": {
    "call": {
      "id": "call_2DbBs7GWhGvVNJGrDXr5RG0mBWI",
      "direction": "outbound",
      "from": "+15551234567",
      "to": "+15559876543",
      "fromChannel": "pstn",
      "toChannel": "pstn"
    },
    "app": {
      "id": "app_2DbBs7GWhGvVNJGrDXr5RG0mBWI"
    },
    "error": {
      "type": "provider_error",
      "code": "call_failed",
      "message": "The call could not be connected. The destination number may be unreachable."
    }
  }
}

calls.busy

Fired when the destination is busy.
calls.busy payload
{
  "event": "calls.busy",
  "eventId": "evt_7IgGx2LBmLAASONwICw0WL5rGBN",
  "timestamp": "2024-01-15T10:30:04.000Z",
  "data": {
    "call": {
      "id": "call_2DbBs7GWhGvVNJGrDXr5RG0mBWI",
      "direction": "outbound",
      "from": "+15551234567",
      "to": "+15559876543",
      "fromChannel": "pstn",
      "toChannel": "pstn"
    },
    "app": {
      "id": "app_2DbBs7GWhGvVNJGrDXr5RG0mBWI"
    }
  }
}

calls.no_answer

Fired when no one answers the call.
calls.no_answer payload
{
  "event": "calls.no_answer",
  "eventId": "evt_8JhHy3MCnMBBTPOxJDx1XM6sHCO",
  "timestamp": "2024-01-15T10:30:30.000Z",
  "data": {
    "call": {
      "id": "call_2DbBs7GWhGvVNJGrDXr5RG0mBWI",
      "direction": "outbound",
      "from": "+15551234567",
      "to": "+15559876543",
      "fromChannel": "pstn",
      "toChannel": "pstn"
    },
    "app": {
      "id": "app_2DbBs7GWhGvVNJGrDXr5RG0mBWI"
    }
  }
}

calls.canceled

Fired when a call is canceled before it connects.
calls.canceled payload
{
  "event": "calls.canceled",
  "eventId": "evt_9KiIz4NDoPCCUQPyKEy2YN7tIDP",
  "timestamp": "2024-01-15T10:30:03.000Z",
  "data": {
    "call": {
      "id": "call_2DbBs7GWhGvVNJGrDXr5RG0mBWI",
      "direction": "outbound",
      "from": "+15551234567",
      "to": "+15559876543",
      "fromChannel": "pstn",
      "toChannel": "pstn"
    },
    "app": {
      "id": "app_2DbBs7GWhGvVNJGrDXr5RG0mBWI"
    }
  }
}

Participant Events

These events track participants joining and leaving a call.

calls.participant_joined

Fired when a participant joins the call.
calls.participant_joined payload
{
  "event": "calls.participant_joined",
  "eventId": "evt_0LjJA5OEpQDDVRQzLFz3ZO8uJEQ",
  "timestamp": "2024-01-15T10:30:05.000Z",
  "data": {
    "call": {
      "id": "call_2DbBs7GWhGvVNJGrDXr5RG0mBWI",
      "direction": "outbound",
      "from": "+15551234567",
      "to": "+15559876543",
      "fromChannel": "pstn",
      "toChannel": "pstn"
    },
    "participant": {
      "id": "part_abc123",
      "type": "phone",
      "displayName": "John Doe",
      "phoneNumber": "+15559876543",
      "joinedAt": "2024-01-15T10:30:05.000Z"
    },
    "app": {
      "id": "app_2DbBs7GWhGvVNJGrDXr5RG0mBWI"
    }
  }
}

calls.participant_left

Fired when a participant leaves the call. Includes the participant’s duration and leftAt timestamp.
calls.participant_left payload
{
  "event": "calls.participant_left",
  "eventId": "evt_1MkKB6PFqREEWSRAMGA4AP9vKFR",
  "timestamp": "2024-01-15T10:32:00.000Z",
  "data": {
    "call": {
      "id": "call_2DbBs7GWhGvVNJGrDXr5RG0mBWI",
      "direction": "outbound",
      "from": "+15551234567",
      "to": "+15559876543",
      "fromChannel": "pstn",
      "toChannel": "pstn"
    },
    "participant": {
      "id": "part_abc123",
      "type": "phone",
      "displayName": "John Doe",
      "phoneNumber": "+15559876543",
      "joinedAt": "2024-01-15T10:30:05.000Z",
      "duration": 115,
      "leftAt": "2024-01-15T10:32:00.000Z"
    },
    "app": {
      "id": "app_2DbBs7GWhGvVNJGrDXr5RG0mBWI"
    }
  }
}

Recording and Voicemail Events

calls.recording.completed

Fired when a call recording has finished processing and is available for download.
calls.recording.completed payload
{
  "event": "calls.recording.completed",
  "eventId": "evt_2NlLC7QGrSFFXTSBNHB5BQ0wLGS",
  "timestamp": "2024-01-15T10:33:00.000Z",
  "data": {
    "call": {
      "id": "call_2DbBs7GWhGvVNJGrDXr5RG0mBWI",
      "direction": "outbound",
      "from": "+15551234567",
      "to": "+15559876543",
      "fromChannel": "pstn",
      "toChannel": "pstn"
    },
    "recording": {
      "id": "call_rec_2DbBs7GWhGvVNJGrDXr5RG0mBWI",
      "duration": 120,
      "url": "https://storage.buildwithchirp.com/recordings/call_rec_2DbBs7GWhGvVNJGrDXr5RG0mBWI.mp4?signed=...",
      "format": "mp4",
      "size": 1048576
    },
    "app": {
      "id": "app_2DbBs7GWhGvVNJGrDXr5RG0mBWI"
    }
  }
}

calls.voicemail.received

Fired when a voicemail is left. Includes the voicemail audio URL and transcription (when available).
calls.voicemail.received payload
{
  "event": "calls.voicemail.received",
  "eventId": "evt_3OmMD8RHsTGGYUSCOIC6CR1xMHT",
  "timestamp": "2024-01-15T10:31:30.000Z",
  "data": {
    "call": {
      "id": "call_2DbBs7GWhGvVNJGrDXr5RG0mBWI",
      "direction": "inbound",
      "from": "+15559876543",
      "to": "+15551234567",
      "fromChannel": "pstn",
      "toChannel": "pstn"
    },
    "voicemail": {
      "id": "call_vm_2DbBs7GWhGvVNJGrDXr5RG0mBWI",
      "duration": 30,
      "url": "https://storage.buildwithchirp.com/voicemails/call_vm_2DbBs7GWhGvVNJGrDXr5RG0mBWI.mp4?signed=...",
      "transcription": "Hi, please call me back when you get a chance."
    },
    "app": {
      "id": "app_2DbBs7GWhGvVNJGrDXr5RG0mBWI"
    }
  }
}
The transcription field may be null if transcription is not enabled or still processing. You can retrieve the transcription later using the voicemail API endpoints.
The url fields in recording and voicemail webhook payloads are time-limited signed URLs. If you need to access the file later, use the Recordings API to generate a fresh URL.

Event Summary

EventDescription
calls.initiatedCall created (outbound) or incoming call received (inbound)
calls.ringingOutbound call is ringing at the destination
calls.answeredCall was answered
calls.completedCall ended normally
calls.failedCall failed with an error
calls.busyDestination was busy
calls.no_answerNo one answered the call
calls.canceledCall was canceled before connecting
calls.participant_joinedA participant joined the call
calls.participant_leftA participant left the call
calls.recording.completedRecording finished processing and is available
calls.voicemail.receivedA voicemail was left

Handling Calling Webhooks

Here is an example of how to handle calling webhooks in your application:
Handle Calling Webhooks
app.post("/webhooks/chirp", (req, res) => {
  // Acknowledge immediately
  res.status(200).send("OK");

  const { event, eventId, data } = req.body;

  switch (event) {
    case "calls.initiated":
      console.log(`Call ${data.call.id} initiated (${data.call.direction})`);
      break;

    case "calls.completed":
      console.log(`Call ${data.call.id} completed after ${data.call.duration}s`);
      break;

    case "calls.failed":
      console.error(`Call ${data.call.id} failed: ${data.error.message}`);
      break;

    case "calls.recording.completed":
      console.log(`Recording available: ${data.recording.url}`);
      break;

    case "calls.voicemail.received":
      console.log(`Voicemail from ${data.call.from}: ${data.voicemail.transcription ?? "(transcription pending)"}`);

      break;

    default:
      console.log(`Unhandled event: ${event}`);
  }
});
Use the eventId field to implement idempotency. Track processed event IDs to prevent handling the same event twice if it is delivered more than once.