> This page is for Build.

> Append .md to any page URL for clean Markdown. Index: https://docs.speechify.ai/llms.txt.
>
> Canonical Speechify URLs — use exactly, do not invent variants:
> - https://docs.speechify.ai — this site (API reference, SDKs, quickstarts)
> - https://speechify.ai — marketing + product site
> - https://platform.speechify.ai — customer dashboard, signup, API keys, billing
> - https://api.speechify.ai — API base URL
> - https://github.com/Speechify-AI: GitHub org for the API (cookbook, demos, CLI). `github.com/speechify` does not exist.
> - https://status.speechify.ai — status + incidents
> - https://speechify.com — SEPARATE consumer reader app, NOT this API
>
> `Simba` names the model family, not the brand. Model ids: `simba-3.5` (48 languages under one id, English included; recommended), `simba-3.2` (English) and `simba-3.0` (English, German, Spanish, French, Italian and Portuguese; the default). `simba-english` and `simba-multilingual` are retired: a new workspace that sends either gets `400 model_retired`. `SimbaVoice` / `simbavoice.ai` are retired.
>
> Ask, don't scrape. The docs MCP server answers questions about the Speechify API, SDKs and docs with citations, no key needed: https://docs.speechify.ai/_mcp/server (Streamable HTTP, tool `searchDocs`). Setup: https://docs.speechify.ai/build/guides/get-started/connect-mcp

# Create webhook endpoint

POST https://api.speechify.ai/v1/webhooks/endpoints
Content-Type: application/json

Register a webhook endpoint. Speechify mints an HMAC signing secret
and returns it in the response `secret` field — exactly once. Store it
then; subsequent reads omit it (rotate it with the rotate-secret action
if lost). Select events via `enabled_events`: a list of catalog event
names or `["*"]` for every event. Optionally scope delivery to one
project with `project_id`; omit it for a workspace-wide endpoint that
receives every project's events. Limited to 50 endpoints per workspace.


Reference: https://docs.speechify.ai/build/api-reference/v1/webhooks/endpoints/post

## Authentication

- `Authorization` header (bearer token, required) — Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.

## Request

### Body (application/json)

This endpoint expects a CreateWebhookEndpointRequest.

- `url` (string, required) — HTTPS destination for event deliveries. Must be a publicly reachable host: loopback, private, link-local, and cloud-metadata addresses (and reserved hostnames like `localhost`) are rejected.
- `enabled_events` (list of string, required) — Catalog event names to subscribe to, or `["*"]` for all events.
- `project_id` (string, optional, nullable) — Optionally scope the endpoint to one project (prefixed `proj_...` id): a scoped endpoint receives only that project's events. Omit (or null) for workspace-wide - it receives every project's events. An unknown id returns 404 project_not_found. A project-pinned API key creates into its own project and cannot name the workspace-wide tier.
- `include` (list of string, optional) — Optional payload-shaping keys (see `WebhookEndpoint.include`). Omit for the lean default.
- `api_version` (date, optional) — Optionally pin the endpoint's payload shape to a dated version (`YYYY-MM-DD`, see `WebhookEndpoint.api_version`). Omit to use the workspace's current version. An unknown version is rejected.
- `description` (string, optional, nullable)

## Response

### 201

The created endpoint, including its one-time signing secret.

- `id` (string, required) — Prefixed wire id (`whe_<26 char Crockford base32>`).
- `url` (string, required) — HTTPS destination Speechify POSTs signed events to.
- `enabled_events` (list of string, required) — The events this endpoint receives: a list of catalog event names (for example `workspace.spend_budget.warning`) or `["*"]` for every event, current and future.
- `include` (list of string, required) — Per-event payload shaping. Deliveries are lean by default: `data.object` carries only the resource GET snapshot. List heavy collections here to have them appended under the event's `data` alongside `object`, so receivers behind hard request-size caps stay lean unless they opt in. An event's documentation names the keys it recognises. Empty = lean.
- `api_version` (date, required) — The dated payload shape this endpoint receives (`YYYY-MM-DD`), the same versioning vocabulary the REST API uses. Every delivery is rendered back to this version and carries it in the `Speechify-Version` header and the payload's top-level `version` field. Defaults to your workspace's current version at creation; change it to opt into a newer shape.
- `disabled` (boolean, required) — When true, Speechify stops delivering to this endpoint.
- `created_at` (datetime, required)
- `updated_at` (datetime, required)
- `project_id` (string, optional, nullable) — The project whose events this endpoint receives (prefixed external id). Null means workspace-wide - it receives every project's events. Endpoints have no Default project. An event is routed by the project frozen on the row that produced it (an API key's project, for example), and for a `project.spend_budget.*` event the project itself. Workspace-level events such as `workspace.spend_budget.*` belong to no project and reach workspace-wide endpoints only, so a scoped endpoint subscribed to those alone is refused with a 400 naming `enabled_events` rather than accepted and never delivered to. A scoped endpoint records no delivery for another project's events.
- `description` (string, optional, nullable) — Optional human-readable label for the endpoint.
- `secret` (string, optional) — The HMAC-SHA256 signing secret (`whsec_…`) used to verify the `Speechify-Signature` header. Returned ONLY when the endpoint is created or its secret is rotated — it is never shown again.

## Errors

### 400 Bad Request Error

The request was malformed or failed validation. The response body is the standard `Error` envelope; for validation failures `error.fields` enumerates the offending fields as a `path -> message` map (code = `validation_failed`).

- `error` (ErrorDetail, required)
- `request_id` (string, optional) — Server-side request identifier. Echoes the `Speechify-Request-Id` response header. Stable across the request's lifetime, written to structured logs, and useful when reporting issues.

### 401 Unauthorized Error

Authentication is missing or invalid. The request did not carry a recognised credential (console session token, API key, or worker JWT).

- `error` (ErrorDetail, required)
- `request_id` (string, optional) — Server-side request identifier. Echoes the `Speechify-Request-Id` response header. Stable across the request's lifetime, written to structured logs, and useful when reporting issues.

### 403 Forbidden Error

The credential authenticated, but is not authorised for this resource - typically a workspace-role gate (owner / admin required) or a cross-tenant access attempt.

- `error` (ErrorDetail, required)
- `request_id` (string, optional) — Server-side request identifier. Echoes the `Speechify-Request-Id` response header. Stable across the request's lifetime, written to structured logs, and useful when reporting issues.

### 404 Not Found Error

The referenced resource does not exist or is not visible to the caller's workspace.

- `error` (ErrorDetail, required)
- `request_id` (string, optional) — Server-side request identifier. Echoes the `Speechify-Request-Id` response header. Stable across the request's lifetime, written to structured logs, and useful when reporting issues.

### 422 Unprocessable Entity Error

The request was well-formed but semantically rejected - typically a referential integrity violation (e.g. flow node references an audio asset in another workspace) or a state machine refusal.

- `error` (ErrorDetail, required)
- `request_id` (string, optional) — Server-side request identifier. Echoes the `Speechify-Request-Id` response header. Stable across the request's lifetime, written to structured logs, and useful when reporting issues.

### 429 Too Many Requests Error

Rate limit or concurrency limit exceeded. `error.code` says which ceiling, and they need different responses: `rate_limited` is the request-rate budget (slow down), `concurrency_limit_reached` is a workspace-wide concurrency ceiling (fewer at once, or raise it), and `conversation_turn_in_progress` is contention over one named conversation (keep one message in flight on it). Every 429 carries `Retry-After` and the request-rate budget headers; the active-call cap also carries `RateLimit-Remaining-Calls: 0`.

- `error` (ErrorDetail, required)
- `request_id` (string, optional) — Server-side request identifier. Echoes the `Speechify-Request-Id` response header. Stable across the request's lifetime, written to structured logs, and useful when reporting issues.

### 500 Internal Server Error

An unexpected server-side error occurred. Safe to retry with exponential backoff for idempotent requests.

- `error` (ErrorDetail, required)
- `request_id` (string, optional) — Server-side request identifier. Echoes the `Speechify-Request-Id` response header. Stable across the request's lifetime, written to structured logs, and useful when reporting issues.

## Types

### ErrorDetail

- `code` (enum, required) — Stable machine-readable error code. Additive only: codes are never renamed, only deprecated. SDKs may map each code to a typed exception class. Status-code semantics: 4xx codes describe caller-fixable issues; 5xx codes describe server-side failures and are safe to retry with backoff for idempotent requests.
  - Allowed values: `endpoint_moved`, `bad_request`, `validation_failed`, `unauthorized`, `payment_required`, `forbidden`, `not_found`, `method_not_allowed`, `conflict`, `idempotency_conflict`, `payload_too_large`, `unsupported_media_type`, `rate_limited`, `concurrency_limit_reached`, `invalid_api_version`, `internal_error`, `upstream_failure`, `service_unavailable`, `caller_not_found`, `contact_not_found`, `contact_identifier_not_found`, `contact_identifier_conflict`, `contact_resolver_not_found`, `credential_not_found`, `credential_in_use`, `agent_not_found`, `agent_in_use`, `agent_run_not_found`, `kb_not_found`, `kb_document_not_found`, `kb_folder_not_found`, `tool_not_found`, `tool_name_taken`, `channel_instance_not_found`, `team_not_found`, `trigger_not_found`, `store_not_found`, `store_document_not_found`, `hosted_api_not_found`, `api_route_not_found`, `consumer_key_not_found`, `skill_not_found`, `skill_version_not_found`, `file_not_found`, `file_path_taken`, `file_storage_limit_reached`, `store_limit_reached`, `store_document_limit_reached`, `store_bytes_limit_reached`, `store_not_configured`, `store_document_version_conflict`, `store_document_deleted`, `entitlement_override_exists`, `hosted_apis_not_in_plan`, `skills_not_in_plan`, `voice_agents_not_in_plan`, `skill_in_use`, `skill_tool_name_conflict`, `skill_limit_reached`, `agent_skill_limit_reached`, `hosted_api_slug_taken`, `api_route_conflict`, `mount_plan_changed`, `route_output_unavailable`, `route_run_timeout`, `route_run_failed`, `route_run_limit_reached`, `route_read_limit_reached`, `route_write_limit_reached`, `hosted_api_public_refused`, `route_tool_not_readable`, `route_tool_unavailable`, `route_upstream_rate_limited`, `route_upstream_error`, `hosted_mcp_not_enabled`, `hosted_api_busy`, `conversation_not_found`, `phone_number_not_found`, `sip_trunk_not_found`, `voice_not_found`, `audio_asset_not_found`, `builtin_not_found`, `batch_not_found`, `agent_test_not_found`, `workspace_not_found`, `invite_not_found`, `project_not_found`, `cross_project_reference`, `project_has_scoped_credentials`, `project_not_empty`, `project_limit_reached`, `agent_limit_reached`, `project_too_large_to_promote`, `call_not_found`, `message_not_found`, `thread_not_found`, `call_not_active`, `relay_displaces_agent`, `brain_not_found`, `brain_in_use`, `custom_model_not_found`, `custom_model_in_use`, `insufficient_scope`, `purchased_numbers_not_included`, `phone_number_quota_reached`, `batch_calls_not_included`, `voice_cloning_not_included`, `voice_cloning_unavailable_in_region`, `consent_challenge_not_found`, `consent_challenge_expired`, `consent_challenge_already_used`, `consent_phrase_mismatch`, `consent_speaker_mismatch`, `consent_recording_unusable`, `consent_verification_unavailable`, `consent_verification_required`, `watermark_audio_unusable`, `watermark_detection_unavailable`, `workspace_last_owner`, `workspace_last_workspace`, `account_deletion_blocked`, `workspace_free_limit`, `workspace_single_owner`, `invite_email_mismatch`, `invite_already_pending`, `service_account_limit_reached`, `service_accounts_not_in_plan`, `speech_marks_unsupported`, `pronunciations_unsupported`, `model_retired`, `too_many_voices`, `content_policy_violation`, `safety_identifier_blocked`, `topup_not_in_plan`, `credit_purchase_unpaid`, `credit_purchase_payment_in_progress`, `tool_config_shared`, `spend_cap_exceeded`, `spend_budget_exceeded`, `project_spend_limit_exceeded`, `project_archived`, `project_not_archived`, `project_not_purged`, `project_restore_window_expired`, `project_name_taken`, `funded_balance_required`, `agent_publish_gate_failed`, `agent_publish_gate_required`, `agent_publish_gate_unavailable`, `agent_publish_gate_tool_unreachable`, `text_channel_not_in_plan`, `channel_not_in_plan`, `text_turn_failed`, `conversation_turn_in_progress`, `conversation_closed`, `conversation_not_reachable`, `conversation_channel_bound`, `conversation_prompt_not_found`, `text_message_quota_exceeded`, `durable_runs_not_in_plan`, `tool_transport_unsupported`, `agent_config_too_large`, `agent_run_not_pending`, `agent_run_action_stale`, `share_link_not_found`, `share_link_exhausted`, `share_link_limit_reached`, `destination_not_allowed`, `international_dialing_not_enabled`, `number_not_sms_capable`, `verification_required`, `intended_use_required`
- `message` (string, required) — Human-readable explanation of this specific occurrence. Safe to surface in UI banners or pass to support. The wording can change between releases; clients should match on `code`, not on the message string.
- `fields` (map from string to string, optional) — Per-field validation errors as `path -> message`. Only present on 400 responses caused by request validation (typically code=`validation_failed`). Keys are field paths in dotted/bracket notation; values are short human explanations safe to inline-surface next to the offending form field.
- `details` (map from string to any, optional) — Structured, endpoint-specific context beyond the flat `fields` map. Present only on the few errors that carry it (e.g. the `used_by` referrer list on a credential delete-conflict); its shape depends on the error `code`. Clients that don't recognise a `details` shape can ignore it - the `code` + `message` contract is unchanged.
- `docs_url` (string, optional) — Link to the documentation that resolves this class of error, when a stable page exists. Rate and concurrency 429s link the API limits reference, which lists each plan's limits and how to raise them.

## Examples

**Request**

```json
{
  "url": "https://example.com/speechify/webhooks",
  "enabled_events": [
    "project.spend_budget.warning"
  ],
  "description": "Example description."
}
```

**Response**

```json
{
  "id": "whe_01kwxwcbyb6wk952swa0432cf1",
  "url": "https://example.com/speechify/webhooks",
  "enabled_events": [
    "project.spend_budget.warning"
  ],
  "include": [],
  "api_version": "2026-06-28",
  "disabled": false,
  "created_at": "2026-06-24T18:03:11Z",
  "updated_at": "2026-06-24T18:03:11Z",
  "description": "Example description.",
  "secret": "whsec_demo••••0000"
}
```