curl --request POST \
--url https://api.contourvoice.com/v1/webhook-endpoints \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"agent_ids": [
"3f1c1b2e-6d1a-4c0e-9b2f-1a2b3c4d5e6f"
],
"description": "Production",
"events": [
"call.started",
"call.ended",
"call.analyzed"
],
"url": "https://example.com/webhooks/contour"
}
'import requests
url = "https://api.contourvoice.com/v1/webhook-endpoints"
payload = {
"agent_ids": ["3f1c1b2e-6d1a-4c0e-9b2f-1a2b3c4d5e6f"],
"description": "Production",
"events": ["call.started", "call.ended", "call.analyzed"],
"url": "https://example.com/webhooks/contour"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
agent_ids: ['3f1c1b2e-6d1a-4c0e-9b2f-1a2b3c4d5e6f'],
description: 'Production',
events: ['call.started', 'call.ended', 'call.analyzed'],
url: 'https://example.com/webhooks/contour'
})
};
fetch('https://api.contourvoice.com/v1/webhook-endpoints', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.contourvoice.com/v1/webhook-endpoints",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'agent_ids' => [
'3f1c1b2e-6d1a-4c0e-9b2f-1a2b3c4d5e6f'
],
'description' => 'Production',
'events' => [
'call.started',
'call.ended',
'call.analyzed'
],
'url' => 'https://example.com/webhooks/contour'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.contourvoice.com/v1/webhook-endpoints"
payload := strings.NewReader("{\n \"agent_ids\": [\n \"3f1c1b2e-6d1a-4c0e-9b2f-1a2b3c4d5e6f\"\n ],\n \"description\": \"Production\",\n \"events\": [\n \"call.started\",\n \"call.ended\",\n \"call.analyzed\"\n ],\n \"url\": \"https://example.com/webhooks/contour\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.contourvoice.com/v1/webhook-endpoints")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"agent_ids\": [\n \"3f1c1b2e-6d1a-4c0e-9b2f-1a2b3c4d5e6f\"\n ],\n \"description\": \"Production\",\n \"events\": [\n \"call.started\",\n \"call.ended\",\n \"call.analyzed\"\n ],\n \"url\": \"https://example.com/webhooks/contour\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.contourvoice.com/v1/webhook-endpoints")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"agent_ids\": [\n \"3f1c1b2e-6d1a-4c0e-9b2f-1a2b3c4d5e6f\"\n ],\n \"description\": \"Production\",\n \"events\": [\n \"call.started\",\n \"call.ended\",\n \"call.analyzed\"\n ],\n \"url\": \"https://example.com/webhooks/contour\"\n}"
response = http.request(request)
puts response.read_body{
"agent_ids": [
"3f1c1b2e-6d1a-4c0e-9b2f-1a2b3c4d5e6f"
],
"created_at": "2026-09-10T14:00:00Z",
"description": "Production",
"enabled": true,
"events": [
"call.started",
"call.ended",
"call.analyzed"
],
"id": "b7e1c2d3-4f5a-4b6c-8d7e-9f0a1b2c3d4e",
"secret": "whsec_x9K2mQ7vR4tL8nP1sW5yB3dF6hJ0k9Qz",
"secret_hint": "k9Qz",
"updated_at": "2026-09-10T14:00:00Z",
"url": "https://example.com/webhooks/contour"
}{
"detail": "Invalid or expired API key"
}{
"detail": "url must use https."
}Create a webhook endpoint
Register a URL to receive events.
The response includes the signing secret once. Store it in your
webhook handler’s configuration; it is never returned again. If you lose
it, rotate the secret to get a new one.
The URL must be https and publicly reachable. Omit agent_ids to receive
events for every call in your organization, including calls started from
the Contour dashboard; use call.source on the payload to tell them apart.
curl --request POST \
--url https://api.contourvoice.com/v1/webhook-endpoints \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"agent_ids": [
"3f1c1b2e-6d1a-4c0e-9b2f-1a2b3c4d5e6f"
],
"description": "Production",
"events": [
"call.started",
"call.ended",
"call.analyzed"
],
"url": "https://example.com/webhooks/contour"
}
'import requests
url = "https://api.contourvoice.com/v1/webhook-endpoints"
payload = {
"agent_ids": ["3f1c1b2e-6d1a-4c0e-9b2f-1a2b3c4d5e6f"],
"description": "Production",
"events": ["call.started", "call.ended", "call.analyzed"],
"url": "https://example.com/webhooks/contour"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
agent_ids: ['3f1c1b2e-6d1a-4c0e-9b2f-1a2b3c4d5e6f'],
description: 'Production',
events: ['call.started', 'call.ended', 'call.analyzed'],
url: 'https://example.com/webhooks/contour'
})
};
fetch('https://api.contourvoice.com/v1/webhook-endpoints', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.contourvoice.com/v1/webhook-endpoints",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'agent_ids' => [
'3f1c1b2e-6d1a-4c0e-9b2f-1a2b3c4d5e6f'
],
'description' => 'Production',
'events' => [
'call.started',
'call.ended',
'call.analyzed'
],
'url' => 'https://example.com/webhooks/contour'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.contourvoice.com/v1/webhook-endpoints"
payload := strings.NewReader("{\n \"agent_ids\": [\n \"3f1c1b2e-6d1a-4c0e-9b2f-1a2b3c4d5e6f\"\n ],\n \"description\": \"Production\",\n \"events\": [\n \"call.started\",\n \"call.ended\",\n \"call.analyzed\"\n ],\n \"url\": \"https://example.com/webhooks/contour\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.contourvoice.com/v1/webhook-endpoints")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"agent_ids\": [\n \"3f1c1b2e-6d1a-4c0e-9b2f-1a2b3c4d5e6f\"\n ],\n \"description\": \"Production\",\n \"events\": [\n \"call.started\",\n \"call.ended\",\n \"call.analyzed\"\n ],\n \"url\": \"https://example.com/webhooks/contour\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.contourvoice.com/v1/webhook-endpoints")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"agent_ids\": [\n \"3f1c1b2e-6d1a-4c0e-9b2f-1a2b3c4d5e6f\"\n ],\n \"description\": \"Production\",\n \"events\": [\n \"call.started\",\n \"call.ended\",\n \"call.analyzed\"\n ],\n \"url\": \"https://example.com/webhooks/contour\"\n}"
response = http.request(request)
puts response.read_body{
"agent_ids": [
"3f1c1b2e-6d1a-4c0e-9b2f-1a2b3c4d5e6f"
],
"created_at": "2026-09-10T14:00:00Z",
"description": "Production",
"enabled": true,
"events": [
"call.started",
"call.ended",
"call.analyzed"
],
"id": "b7e1c2d3-4f5a-4b6c-8d7e-9f0a1b2c3d4e",
"secret": "whsec_x9K2mQ7vR4tL8nP1sW5yB3dF6hJ0k9Qz",
"secret_hint": "k9Qz",
"updated_at": "2026-09-10T14:00:00Z",
"url": "https://example.com/webhooks/contour"
}{
"detail": "Invalid or expired API key"
}{
"detail": "url must use https."
}Authorizations
Your organization's API key (sk_cont_...), sent as Authorization: Bearer sk_cont_.... Keep it server-side.
Body
Which events to receive. Any subset of call.started, call.ended, call.analyzed.
HTTPS URL we POST events to. Must be publicly reachable; private and loopback addresses are rejected.
Only deliver events for calls made by these agents. Omit (or send null) to receive events for every call in your organization, including calls started from the Contour dashboard.
A label for your own reference.
500Response
Successful Response
Returned by create and rotate-secret only. secret is shown once.
False while deliveries are paused.
Subscribed events.
Endpoint id; use it in every other endpoint call.
The signing secret (whsec_...). Shown in this response only; store it in your webhook handler's configuration.
The last four characters of the signing secret, so you can tell which secret an endpoint is using without exposing it.
Where events are delivered.
Agent filter, or null for all calls in the organization.
Your label.