diff --git a/docs/src/content/codesnippets/webhooks-security.txt b/docs/src/content/codesnippets/webhooks-security.txt new file mode 100644 index 0000000..96efb2a --- /dev/null +++ b/docs/src/content/codesnippets/webhooks-security.txt @@ -0,0 +1,12 @@ +import datetime +import hmac + +request_timestamp = request.headers["X-Ultravox-Webhook-Timestamp"] +if datetime.datetime.now() - datetime.dateimte.fromisoformat(request_timestamp) > datetime.timedelta(minutes=1): + raise RuntimeError("Expired message") +expected_signature = hmac.new(WEBHOOK_SECRET.encode(), request.content + request_timestamp.encode(), "sha256").hexdigest() +for signature in request.headers["X-Ultravox-Webhook-Signature"].split(","): + if hmac.compare_digest(signature, expected_signature): + break # Valid signature +else: + raise RuntimeError("Message or timestamp was tampered with") \ No newline at end of file diff --git a/docs/src/content/docs/api/webhooks.mdx b/docs/src/content/docs/api/webhooks.mdx new file mode 100644 index 0000000..e5bf109 --- /dev/null +++ b/docs/src/content/docs/api/webhooks.mdx @@ -0,0 +1,501 @@ +--- +title: "Webhooks" +sidebar: + order: 60 +tableOfContents: true +--- +import { Badge, Tabs, TabItem, Code } from '@astrojs/starlight/components'; +import CallOut from '@components/CallOut.astro'; + + +
+ +Webhooks allow you to receive real-time notifications about events in your Ultravox account. You can configure webhooks to send HTTP POST requests to a specified URL when certain events occur. + +## Webhooks in Ultravox +When creating a webhook, you must provide a URL that will receive the webhook notification. Your service must return an acceptable HTTP status code. + +### HTTP Status Codes +Your service should return a 2xx status code (we recommend 204) to confirm receipt of all webhooks. Any 4xx or 5xx response will result in a [retry](#retries). + +### Securing Webhooks +You can optionally choose to secure your webhooks with a key. When creating a webhook, a secret key is automatically generated for you or you can choose to provide your own secret. You can update or patch your webhooks to change secrets in the event of a leak or as part of regular key rotation. + +Each time your server receives an incoming webhook from Ultravox here's how you can ensure the webhook was sent by Ultravox and hasn't been tampered with: + +1. **Timestamp Verification** +* Each incoming webhook request includes a `X-Ultravox-Webhook-Timestamp` header with the time the webhook was sent. +* Verify that this timestamp is recent (e.g. within the last minute) to prevent replay attacks. + +2. **Signature Verification** +* Ultravox signs each webhook using HMAC-SHA256. +* The signature is included in the `X-Ultravox-Webhook-Signature` header. +* To verify the signature: + * Concatenate the raw request body with the timestamp. + * Create an HMAC-SHA256 hash of this concatenated string using your webhook secret as the key. + * Compare this hash with the provided signature. + +import whSecuring from '../../codesnippets/webhooks-security.txt?raw'; +export const title = "Example: Verifying Webhook Signature" + + + +3. **Multiple Signatures** +* `The X-Ultravox-Webhook-Signature` header may contain multiple signatures separated by commas. +* This allows for key rotation without downtime. +* Your code should check if any of the provided signatures match your computed signature. + +By implementing these checks, you ensure that only authentic, recent, and unmodified webhooks from Ultravox are processed by your system. +Remember to store your webhook secret securely and never expose it in client-side code or public repositories. + + +### Retries +When a destination URL fails to respond to an incoming webhook with a 200, we will retry using a random exponential backoff strategy as follows: + +* First retry will occur approximately 30 seconds later. +* Subsequent retries will double the retry interval. (e.g. second retry again after 1m, third retry after 2m, etc.) +* Total of 10 retries. + +If your receiving service is still down, you can use the API to retrieve information about any calls that occurred. + +## List Webhooks + + + +Lists all webhooks configured for your account. Account scoped. + + + + + + + + + + + + +
cursor(Optional) Pagination cursor.
+
+ + + + + + + + + + + + +
None
+
+ + Response contains a "results" array of webhook objects. Each object contains: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
webhookIdstringUnique identifier for the webhook.
createdstringDatetime in UTC when the webhook was created.
urlstringThe URL where webhook events will be sent.
secretsarrayArray of secret strings used for webhook authentication.
eventsarrayArray of event types this webhook is subscribed to.
+
+
+ +## Create Webhook + + + +Creates a new webhook configuration for your account. + + + + + + + + + + + + + + +
None
+
+ + + + + + + + + + + + + + + + + + + + + + +
url
required
stringThe URL where webhook events will be sent. Must be a valid URI and maximum 200 characters.
secretsarrayArray of secret strings used for webhook authentication. Each string must be maximum 120 characters.
events
required
arrayArray of event types to subscribe to. Valid values are "call.started" and "call.ended".
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
webhookIdstringUnique identifier for the created webhook.
createdstringDatetime in UTC when the webhook was created.
urlstringThe URL where webhook events will be sent.
secretsarrayArray of secret strings used for webhook authentication.
eventsarrayArray of event types this webhook is subscribed to.
+
+
+ +## Get Webhook + + + +Retrieves details for a specific webhook configuration. + + + + + + + + + + + + +
webhookId
required
Unique identifier for the webhook to retrieve.
+
+ + + + + + + + + + + + +
None
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
webhookIdstringUnique identifier for the webhook.
createdstringDatetime in UTC when the webhook was created.
urlstringThe URL where webhook events will be sent.
secretsarrayArray of secret strings used for webhook authentication.
eventsarrayArray of event types this webhook is subscribed to.
+
+
+ +## Update Webhook + + + +Updates an existing webhook configuration. Completely replaces the webhook. For partial modifications, see [Patch Webhook](#patch-webhook) + + + + + + + + + + + + +
webhookId
required
Unique identifier for the webhook to update.
+
+ + + + + + + + + + + + + + + + + + + + + + +
url
required
stringThe new URL where webhook events will be sent. Must be a valid URI and maximum 200 characters.
secretsarrayNew array of secret strings used for webhook authentication. Each string must be maximum 120 characters.
events
required
arrayNew array of event types to subscribe to. Valid values are "call.started" and "call.ended".
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
webhookIdstringUnique identifier for the updated webhook.
createdstringDatetime in UTC when the webhook was originally created.
urlstringThe updated URL where webhook events will be sent.
secretsarrayUpdated array of secret strings used for webhook authentication.
eventsarrayUpdated array of event types this webhook is subscribed to.
+
+
+ +## Patch Webhook + + + +Updates an existing webhook configuration. Allows partial modifications to the webhook. + + + + + + + + + + + + +
webhookId
required
Unique identifier for the webhook to patch.
+
+ + + + + + + + + + + + + + + + + + + + + + +
url
required
stringThe new URL where webhook events will be sent. Must be a valid URI and maximum 200 characters.
secretsarrayNew array of secret strings used for webhook authentication. Each string must be maximum 120 characters.
events
required
arrayNew array of event types to subscribe to. Valid values are "call.started" and "call.ended".
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
webhookIdstringUnique identifier for the patched webhook.
createdstringDatetime in UTC when the webhook was originally created.
urlstringThe updated URL where webhook events will be sent.
secretsarrayUpdated array of secret strings used for webhook authentication.
eventsarrayUpdated array of event types this webhook is subscribed to.
+
+
+ +## Delete Webhook + + + +Deletes an existing webhook configuration. + + + + + + + + + + + + +
webhookId
required
Unique identifier for the webhook to delete.
+
+ + + + + + + + +
No request body
+
+ + + + + + + + +
No response body (204 No Content)
+
+
+ +## Available Events +The following events are available and can be specified when creating or updating a webhoook. + +| event | description | +|------------- | -------------------------- | +| call.started | Fired when a call starts. | +| call.ended | Fired when a call ends. | + +## Webhook Payloads +The payloads that are sent with each webhook are passed in the request body as follows: + +```js +{ + "call": {call_object} +} +``` + +The `{call_object}` matches what is returned from the [Get Call](../calls/#get-call) endpoint. \ No newline at end of file