> ## Documentation Index
> Fetch the complete documentation index at: https://docs.talkturo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Talkturo webhooks for call events

> Receive real-time HTTP callbacks for call status changes and billing events. Configure webhook URLs in your dashboard and verify payloads with signatures.

Webhooks let Talkturo push events to your server in real time, so you do not need to poll the API for updates. Talkturo fires two categories of webhook: call status events from the telephony layer, and billing events from your subscription provider. You configure the target URL for each webhook type in the Talkturo dashboard, and Talkturo sends a signed `POST` request to that URL when an event occurs.

***

## Configure webhook URLs

<Steps>
  <Step title="Open Integrations settings">
    In the Talkturo dashboard, navigate to **Settings** → **Integrations**.
  </Step>

  <Step title="Enter your endpoint URL">
    In the **Webhooks** section, enter the publicly reachable HTTPS URL of your server for each webhook type. The URL must return a `200` status code within 5 seconds or Talkturo will treat the delivery as failed.
  </Step>

  <Step title="Save and test">
    Click **Save**. Use the **Send test event** button to verify your endpoint receives and processes the payload correctly.
  </Step>
</Steps>

<Warning>
  Your webhook endpoint must be reachable over the public internet. Localhost URLs will not receive events in production.
</Warning>

***

## Webhook security

Talkturo includes a cryptographic signature in the `Telnyx-Signature-Ed25519` header (for call webhooks) and a `X-Signature` header (for billing webhooks). Verify this signature before processing the payload to confirm the request originated from Talkturo and was not tampered with.

The signature is a base64-encoded HMAC or Ed25519 signature computed over the raw request body. Compare it against a signature you compute using your webhook secret, available in the dashboard under **Settings** → **Integrations** → **Webhook Secrets**.

<Note>
  Always verify signatures in production. Skipping signature verification exposes your endpoint to spoofed events.
</Note>

***

## Call status webhook

`POST /api/db/webhook`

Talkturo sends call status events to this endpoint as calls progress through their lifecycle. Configure the URL your server exposes for this path in the dashboard.

### Event types

| Event                          | Description                                            |
| ------------------------------ | ------------------------------------------------------ |
| `call.initiated`               | An outbound call was placed or an inbound call arrived |
| `call.answered`                | The callee picked up                                   |
| `call.completed`               | The call ended normally                                |
| `call.hangup`                  | One party disconnected the call                        |
| `call.machine_detection.ended` | Answering machine detection completed                  |
| `call.recording.saved`         | A call recording was saved to storage                  |

### Payload structure

```json theme={null}
{
  "type": "call.completed",
  "data": {
    "call_control_id": "v2:T02llLL...",
    "call_leg_id": "0cf8a7d8-...",
    "call_session_id": "84a7e8b2-...",
    "client_state": "eyJhc3Npc3RhbnRJZCI6...",
    "connection_id": "1234567890",
    "from": "+14155550101",
    "to": "+12025551234",
    "direction": "outbound",
    "start_time": "2024-01-15T14:00:00Z",
    "end_time": "2024-01-15T14:04:32Z",
    "duration_secs": 272
  }
}
```

| Field                  | Type    | Description                                                |
| ---------------------- | ------- | ---------------------------------------------------------- |
| `type`                 | string  | The event type (for example, `call.completed`)             |
| `data.call_control_id` | string  | Telnyx call control identifier                             |
| `data.call_leg_id`     | string  | Unique identifier for this call leg                        |
| `data.call_session_id` | string  | Groups all legs in the same session                        |
| `data.client_state`    | string  | Base64-encoded metadata passed when the call was initiated |
| `data.connection_id`   | string  | The Telnyx connection the call used                        |
| `data.from`            | string  | Caller number in E.164 format                              |
| `data.to`              | string  | Callee number in E.164 format                              |
| `data.direction`       | string  | `inbound` or `outbound`                                    |
| `data.start_time`      | string  | ISO 8601 datetime the call started                         |
| `data.end_time`        | string  | ISO 8601 datetime the call ended (`call.completed` only)   |
| `data.duration_secs`   | integer | Call duration in seconds (`call.completed` only)           |

### Example handler

```javascript theme={null}
import { createHmac } from 'crypto';

export async function POST(request) {
  const body = await request.text();
  const signature = request.headers.get('Telnyx-Signature-Ed25519');

  // Verify the signature using your webhook secret from the dashboard
  const isValid = verifyTelnyxSignature(body, signature, process.env.WEBHOOK_SECRET);
  if (!isValid) {
    return new Response('Unauthorized', { status: 401 });
  }

  const event = JSON.parse(body);

  switch (event.type) {
    case 'call.completed':
      await handleCallCompleted(event.data);
      break;
    case 'call.answered':
      await handleCallAnswered(event.data);
      break;
  }

  return new Response('OK', { status: 200 });
}
```

***

## Billing webhook

`POST /api/billing/webhook`

Talkturo sends subscription and payment events to this endpoint when your billing status changes. Talkturo supports both Stripe and LemonSqueezy as billing providers.

### Event types

| Event                    | Description                                |
| ------------------------ | ------------------------------------------ |
| `subscription.created`   | A new subscription was activated           |
| `subscription.updated`   | Subscription plan or status changed        |
| `subscription.cancelled` | Subscription was cancelled                 |
| `subscription.expired`   | Subscription reached end of billing period |
| `invoice.paid`           | A payment was successfully collected       |
| `invoice.payment_failed` | A payment attempt failed                   |

### Payload structure

```json theme={null}
{
  "type": "subscription.created",
  "data": {
    "subscription_id": "sub_...",
    "customer_id": "cus_...",
    "plan_name": "Growth",
    "status": "active",
    "current_period_start": "2024-01-15T00:00:00Z",
    "current_period_end": "2024-02-15T00:00:00Z",
    "metadata": {
      "team_id": "team_01j..."
    }
  }
}
```

| Field                       | Type   | Description                                                |
| --------------------------- | ------ | ---------------------------------------------------------- |
| `type`                      | string | The billing event type                                     |
| `data.subscription_id`      | string | The subscription identifier from your billing provider     |
| `data.customer_id`          | string | The customer identifier from your billing provider         |
| `data.plan_name`            | string | The name of the plan (for example, `Growth`, `Enterprise`) |
| `data.status`               | string | Subscription status (`active`, `cancelled`, `past_due`)    |
| `data.current_period_start` | string | ISO 8601 start of the current billing period               |
| `data.current_period_end`   | string | ISO 8601 end of the current billing period                 |
| `data.metadata.team_id`     | string | The Talkturo team ID associated with this subscription     |

### Example handler

```javascript theme={null}
export async function POST(request) {
  const body = await request.text();
  const signature = request.headers.get('X-Signature');

  // Verify the signature using your billing webhook secret
  const isValid = verifySignature(body, signature, process.env.WEBHOOK_SECRET);
  if (!isValid) {
    return new Response('Unauthorized', { status: 401 });
  }

  const event = JSON.parse(body);

  switch (event.type) {
    case 'subscription.created':
      await activateTeamFeatures(event.data.metadata.team_id, event.data.plan_name);
      break;
    case 'subscription.cancelled':
      await downgradeTeamFeatures(event.data.metadata.team_id);
      break;
  }

  return new Response('OK', { status: 200 });
}
```

***

## Retry behavior

If your endpoint does not return a `2xx` status code within 5 seconds, Talkturo retries the delivery with exponential backoff. To avoid processing duplicate events, make your webhook handler idempotent — check whether you have already processed a given event ID before acting on it.
