Intro

Webhooks are how services notify each other of events. At their core they are just a POST request to a pre-determined endpoint. The endpoint can be whatever you want, and you can just add them from the UI. You normally use one endpoint per service, and that endpoint listens to all of the event types. For example, if you receive webhooks from Acme Inc., you can structure your URL like: https://www.example.com/acme/webhooks/. The way to indicate that a webhook has been processed is by returning a 2xx (status code 200-299) response to the webhook message within a reasonable time-frame (15s). It’s also important to disable CSRF protection for this endpoint if the framework you use enables them by default. Another important aspect of handling webhooks is to verify the signature and timestamp when processing them. You can learn more about it in the signature verification section.

Adding an Endpoint

In order to start listening to messages, you will need to configure your endpoints. Adding an endpoint is as simple as providing a URL that you control. In the Settings go to the API & Webhooks section and click on “Configure” to get to the webhook portal. Configure Webhooks Click on “Add Endpoint” to add a new webhook address. Add an endpoint

Events

We support the following webhook events:
  • call_ended: Triggered when a call is completed and analyzed
  • contact_status_changed: Triggered when a contact’s status changes

Call Ended Event

When a call is completed and analyzed, we’ll send a webhook with detailed information about the call, including the transcript, analysis results, and complete contact information (in the same format as the /v1/get-contact/{contactId} API endpoint). Call Statuses A call can have the following statuses:
  • COMPLETED: A conversation occurred with the contact
  • ANSWERED: A call occurred but another attempt will be made to reach the contact
  • NOT_REACHED: We weren’t able to reach the contact
  • VOICEMAIL: We got the voicemail of the contact
  • ERROR: An error occurred in the call

Example Payload

{
  "event": "call_ended",
  "call": {
    "call_id": "b4a05730-2abc-4eb0-8066-2e4d23b53ba9",
    "loop_id": "ba99c99d-19b9-4a0d-8df6-3e213088989a",
    "attempt": 1,
    "direction": "outbound",
    "from_number": "+17755719467",
    "to_number": "+16506794960",
    "external_contact_id": "external-123",
    "contact_id": "6bd1e7e0-6d00-4c0b-ad5b-daa72457a27d",
    "agent_id": "d8931604-92ad-45cf-9071-d9cd2afbad0c",
    "triggered_at": 1731956924302,
    "started_at": 1731956932264,
    "booked_slot_for": "2025-02-24T15:30:00Z",
    "ended_at": 1731957002078,
    "call_length_min": 2,
    "call_status": "COMPLETED",
    "transcript": "Agent: Hello...",
    "recording_url": "<url-of-recording>",
    "ended_reason": "contact-ended-call"
    "transcriptObject": [
      {
        "role": "agent",
        "content": "Hello..."
      }
    ],
    "call_analysis": {
      "summary": {
        "value": true,
        "details": "A call between an agent and a customer talking about buying an ice cream machine"
      },
      "appointment": {
        "value": true,
        "details": "2025-02-18T15:30:00Z"
      },
      "interest": {
        "value": true,
        "details": "The customer is interested in buying an ice cream machine"
      }
      // ... other analysis fields
    }
  },
  "contact": {
    "contact_id": "6bd1e7e0-6d00-4c0b-ad5b-daa72457a27d",
    "external_contact_id": "external-123",
    "created_at": "2024-11-18T15:30:00.000Z",
    "gender": "F",
    "first_name": "Frida",
    "last_name": "Kahlo",
    "phone_number": "+14155552671",
    "in_call_since": null,
    "reached_at": "2024-11-18T15:45:00.000Z",
    "contact_details": {
      "company": "Art Studio Inc",
      "notes": "Interested in solar panels"
    },
    "call_attempts": 1,
    "next_call_at": null,
    "email": "[email protected]",
    "timezone": "America/Los_Angeles",
    "salutation": "Ms.",
    "status": "reached"
  }
}

Contact Status Changed Event

The contact status changed event helps you track the progress of contacts through your calling campaign. Each contact moves through different states as they are processed by the system.

Contact Statuses

Contacts can have the following statuses:
  • new: Contact has not been called yet
  • pending: Contact is in dialer and we are trying to reach them
  • closed: Dialer was exhausted and we are not trying to call them anymore
  • reached: Contact was reached and had a conversation. We are not trying to call them anymore

Example Payload

{
  "event": "contact_status_changed",
  "contact": {
    "contact_id": "6bd1e7e0-6d00-4c0b-ad5b-daa72457a27d",
    "external_contact_id": "external-123",
    "status": "reached",
    "status_updated_at": "2024-03-18T15:30:00Z",
    "reached_at": "2024-03-18T15:30:00Z",
    "first_name": "Frida",
    "last_name": "Kahlo",
    "phone_number": "+14155552671",
    "email": "[email protected]"
  }
}

Verifying Signatures

Webhook signatures let you verify that webhook messages are actually sent by us and not a malicious actor. Verifying webhooks is optional but recommended. For a more detailed explanation, check out this article on why you should verify webhooks. Our webhook partner Svix offers a set of useful libraries that make verifying webhooks very simple. Here is a an example using Javascript:
import { Webhook } from "svix";

// You can get this secret from the Webhook Configuration in the telli dashboard
const secret = "whsec_GET_THIS_FROM_THE_DASHBOARD";

// These were all sent from the server
const headers = {
  "svix-id": "msg_p5jXN8AQM9LWM0D4loKWxJek",
  "svix-timestamp": "1614265330",
  "svix-signature": "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=",
};
const payload = '{"test": 2432232314}';

const wh = new Webhook(secret);
// Throws on error, returns the verified content on success
const payload = wh.verify(payload, headers);
For instructions for verifying signatures, check out their webhook verification documentation. The docs include examples in JavaScript, Python, Go, Java, PHP and other languages.

Retries

Automatic Retries

We attempts to deliver each webhook message based on a retry schedule with exponential backoff. The schedule Each message is attempted based on the following schedule, where each period is started following the failure of the preceding attempt:
  • Immediately
  • 5 seconds
  • 5 minutes
  • 30 minutes
  • 2 hours
  • 5 hours
  • 10 hours
  • 10 hours (in addition to the previous)
If an endpoint is removed or disabled delivery attempts to the endpoint will be disabled as well. For example, an attempt that fails three times before eventually succeeding will be delivered roughly 35 minutes and 5 seconds following the first attempt.

Manual retries

You can also use the webhook portal to manually retry each message at any time, or automatically retry (“Recover”) all failed messages starting from a given date.