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

# VoiceInfra Webhooks Reference — Payload and Signatures

> VoiceInfra sends POST requests to your endpoint when calls complete. Reference for payload fields, HMAC-SHA256 verification, and delivery behavior.

This page is the technical reference for VoiceInfra webhook events — covering the full payload schema, request headers, signature verification, and delivery behaviour. If you are looking for instructions on how to configure your webhook endpoint and view delivery logs, see the [Webhooks Setup Guide](/docs/integrations/webhooks).

## Delivery

VoiceInfra delivers webhook events by sending an HTTP `POST` request to the URL you configure in your dashboard. Events fire when a call ends (hang up), regardless of the call outcome.

| Property         | Value                                                              |
| ---------------- | ------------------------------------------------------------------ |
| **Method**       | `POST`                                                             |
| **Content-Type** | `application/json`                                                 |
| **Trigger**      | Call completion (hang up)                                          |
| **Timeout**      | Your endpoint must respond with `200` within the configured window |
| **Retries**      | Failed deliveries are automatically retried                        |

<Note>
  Respond to webhook requests with `HTTP 200` as quickly as possible. Move any time-consuming processing (database writes, third-party API calls) to a background job to avoid timeouts that trigger unnecessary retries.
</Note>

## Request headers

Every webhook delivery includes the following HTTP headers:

| Header                   | Description                                                                |
| ------------------------ | -------------------------------------------------------------------------- |
| `Content-Type`           | `application/json`                                                         |
| `X-VoiceInfra-Signature` | HMAC-SHA256 signature of the raw request body, used to verify authenticity |
| `X-VoiceInfra-Event`     | Event type — currently always `call.completed`                             |

## Payload schema

<ResponseField name="call_id" type="string">
  Unique identifier for the call. Use this to correlate the webhook event with the `call_id` returned when you initiated the call via `POST /calls/outbound`.
</ResponseField>

<ResponseField name="event" type="string">
  Event type. Currently always `call.completed`.
</ResponseField>

<ResponseField name="from_number" type="string">
  The caller's phone number in E.164 format.
</ResponseField>

<ResponseField name="to_number" type="string">
  The called phone number in E.164 format.
</ResponseField>

<ResponseField name="agent_id" type="string">
  The ID of the AI agent that handled the call.
</ResponseField>

<ResponseField name="duration_seconds" type="integer">
  Total call duration in seconds, from answer to hang-up.
</ResponseField>

<ResponseField name="start_time" type="string">
  ISO 8601 timestamp for when the call started.
</ResponseField>

<ResponseField name="end_time" type="string">
  ISO 8601 timestamp for when the call ended.
</ResponseField>

<ResponseField name="call_outcome" type="string">
  One of `answered`, `voicemail`, or `failed`.
</ResponseField>

<ResponseField name="recording_url" type="string">
  URL to the call recording audio file. The file is available for download shortly after the call ends.
</ResponseField>

<ResponseField name="transcript" type="string">
  Full conversation transcript with speaker labels (for example, `Agent:` and `Caller:`).
</ResponseField>

<ResponseField name="summary" type="string">
  AI-generated summary of the call — a concise description of what was discussed and any outcomes reached.
</ResponseField>

<ResponseField name="extracted_data" type="object">
  Key data points extracted by the AI during the call. The fields present in this object vary based on your agent's configuration. For example, a lead qualification agent might extract `{"lead_score": 8, "interested": true}`.
</ResponseField>

### Example payload

```json call.completed payload theme={null}
{
  "call_id": "call_01HXYZ123456",
  "event": "call.completed",
  "from_number": "+14155550100",
  "to_number": "+18005551234",
  "agent_id": "agent_abc123",
  "duration_seconds": 147,
  "start_time": "2025-01-15T14:23:00Z",
  "end_time": "2025-01-15T14:25:27Z",
  "call_outcome": "answered",
  "recording_url": "https://recordings.voiceinfra.ai/call_01HXYZ123456.mp3",
  "transcript": "Agent: Hi Sarah, this is calling to confirm your appointment...\nCaller: Yes, that works for me.",
  "summary": "Customer confirmed appointment for January 15 at 2pm. No changes requested.",
  "extracted_data": {
    "appointment_confirmed": true,
    "customer_name": "Sarah Chen"
  }
}
```

## Signature verification

VoiceInfra signs every webhook payload with HMAC-SHA256 using the webhook secret you set in your dashboard. The signature is included in the `X-VoiceInfra-Signature` header as a hex-encoded string.

To verify a webhook:

1. Read the **raw request body** as bytes (before any JSON parsing).
2. Compute `HMAC-SHA256(raw_body, your_webhook_secret)` and hex-encode the result.
3. Compare your computed digest to the value in `X-VoiceInfra-Signature` using a **constant-time comparison** to prevent timing attacks.
4. Reject the request with `401` if the signatures do not match.

<Warning>
  Always verify the signature before processing a webhook payload. Without verification, your endpoint could process forged or tampered requests from a third party.
</Warning>

```python Python theme={null}
import hmac
import hashlib

def verify_voiceinfra_webhook(payload: bytes, signature: str, secret: str) -> bool:
    """Verify the HMAC-SHA256 signature of a VoiceInfra webhook."""
    expected_sig = hmac.new(
        secret.encode('utf-8'),
        payload,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected_sig, signature)

# Flask example:
# @app.route('/webhook', methods=['POST'])
# def handle_webhook():
#     signature = request.headers.get('X-VoiceInfra-Signature', '')
#     if not verify_voiceinfra_webhook(request.data, signature, WEBHOOK_SECRET):
#         return '', 401
#     payload = request.get_json()
#     # process payload
#     return '', 200
```

```javascript Node.js theme={null}
const crypto = require('crypto');

function verifyVoiceInfraWebhook(payload, signature, secret) {
  const expectedSig = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expectedSig),
    Buffer.from(signature)
  );
}

// Express example:
// app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
//   const sig = req.headers['x-voiceinfra-signature'];
//   if (!verifyVoiceInfraWebhook(req.body, sig, process.env.WEBHOOK_SECRET)) {
//     return res.status(401).send('Invalid signature');
//   }
//   const payload = JSON.parse(req.body);
//   res.status(200).send('OK');
// });
```

<Tip>
  Use `express.raw()` (not `express.json()`) as your body parser middleware so that the raw bytes are available for signature verification before the body is parsed as JSON.
</Tip>

## Responding to webhooks

Your endpoint must return `HTTP 200` to acknowledge receipt. VoiceInfra considers any other status code — including `2xx` codes other than `200` — as a failed delivery and will retry.

**Do heavy processing asynchronously.** Push the payload onto a queue (for example, SQS, BullMQ, or Celery) and return `200` immediately. This keeps your response time well within the timeout window and prevents retries caused by slow downstream operations.

## Delivery logs

View the full history of webhook deliveries in your VoiceInfra dashboard at **Settings → Webhooks → Delivery Logs**. For each delivery you can see:

* **Timestamp** — when the delivery was attempted
* **Event type** — the `X-VoiceInfra-Event` value sent
* **HTTP status code** — the status code your endpoint returned
* **Response time** — how long your endpoint took to respond
* **Request and response payloads** — the full JSON body sent and the response body returned

Use the delivery logs to diagnose failed deliveries, confirm that signature verification is working correctly, and replay specific events during development.
