# Receive webhooks

Have events pushed to your server instead of polling for them.

1**Create an endpoint** with the events you want. The answer holds `secret_key`. It is shown this once, so store it with your other secrets.

**curl**

```bash
curl -X POST https://api.vocatech.com/v1/webhooks \
  -H "Authorization: Bearer $VOCATECH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "CRM sync",
    "url": "https://YOUR_DOMAIN/webhooks/vocatech",
    "event_filters": ["call.ended", "call.transcription", "message.received"]
  }'
```

**Node**

```javascript
import { vt } from './vocatech.mjs';

const hook = await vt('POST', '/v1/webhooks', {
  name: 'CRM sync',
  url: 'https://YOUR_DOMAIN/webhooks/vocatech',
  event_filters: ['call.ended', 'call.transcription', 'message.received'],
});

// hook.secret_key is shown this once. Put it in your secret store now.
console.log('webhook', hook.id, 'created');
```

**Python**

```python
from vocatech import vt

hook = vt("POST", "/v1/webhooks", {
    "name": "CRM sync",
    "url": "https://YOUR_DOMAIN/webhooks/vocatech",
    "event_filters": ["call.ended", "call.transcription", "message.received"],
})

# hook["secret_key"] is shown this once. Put it in your secret store now.
print("webhook", hook["id"], "created")
```

**PHP**

```php
<?php
require __DIR__ . '/vocatech.php';

$hook = vt('POST', '/v1/webhooks', [
    'name'          => 'CRM sync',
    'url'           => 'https://YOUR_DOMAIN/webhooks/vocatech',
    'event_filters' => ['call.ended', 'call.transcription', 'message.received'],
]);

// $hook['secret_key'] is shown this once. Put it in your secret store now.
echo 'webhook ', $hook['id'], " created\n";
```

**Response201 CreatedCopy**

```json
{
  "id": 42,
  "company_id": 1234567890,
  "name": "CRM sync",
  "description": null,
  "url": "https://YOUR_DOMAIN/webhooks/vocatech",
  "event_filters": ["call.ended", "call.transcription", "message.received"],
  "extension_filters": [],
  "number_filters": [],
  "enabled": true,
  "created_at": "2026-09-24 13:40:00",
  "updated_at": "2026-09-24 13:40:00",
  "secret_key": "4f1c2e6a9b0d3f7e8a5c1b2d6e9f0a3c4b7d8e1f2a5c6b9d0e3f4a7b8c1d2e5f"
}
```

2**Verify, answer, then work.** Check the `X-Vocatech-Signature` header, answer `2xx` within 5 seconds, and do the work after you answer. The receivers in [Capture the raw body](https://vocatech.com/developers/guide/webhooks#raw-body) do all three.

3**Dedupe.** A retry repeats the same event `id`, so skip an id you already have. For message events, `data.message_id` names the message itself.

4**Test it.** Send a signed test event to the endpoint. The answer says what your server replied.

**curl**

```bash
curl -X POST https://api.vocatech.com/v1/webhooks/42/test \
  -H "Authorization: Bearer $VOCATECH_API_KEY"
```

**Node**

```javascript
import { vt } from './vocatech.mjs';

const result = await vt('POST', '/v1/webhooks/42/test');
console.log(result.success, result.status_code, result.error);
```

**Python**

```python
from vocatech import vt

result = vt("POST", "/v1/webhooks/42/test")
print(result["success"], result["status_code"], result["error"])
```

**PHP**

```php
<?php
require __DIR__ . '/vocatech.php';

$result = vt('POST', '/v1/webhooks/42/test');
echo $result['success'] ? 'delivered' : 'failed: ' . $result['error'], PHP_EOL;
```

**Response200 OKCopy**

```json
{
  "success": true,
  "status_code": 200,
  "error": null
}
```

You can also manage webhooks in the portal, in company settings under Webhooks. Every event and payload is in [Webhooks in depth](https://vocatech.com/developers/guide/webhooks).
