> For the full page index, fetch https://echonova.ai/llms.txt

# Configure a webhook

Create a webhook endpoint in the portal, choose the events it receives, keep its signing secret, and disable or delete it later.

A webhook is an HTTPS endpoint you own that EchoNova
calls when an asynchronous job finishes or an API key crosses its credit
limit. You create it
in the portal, pick the events it subscribes to, and keep the signing secret so
your server can verify each delivery.

## What you need

- An EchoNova account and a workspace. Sign in at `/auth/sign-in`.
- The workspace owner's access, which includes `developer_api.manage` to
  create, edit or delete webhooks. Viewing the list needs `developer_api.view`.
- A public endpoint that accepts `POST` requests with a JSON body over
  `https`.
- An API key, if you want job events. Only jobs started with an API key are
  announced. See [Get an API key](/docs/developers/get-an-api-key).

## Steps

### Step 1: Open the Webhooks tab

Go to `/developers?tab=webhooks`, or open **Developers** in the portal and
select the **Webhooks** tab. Webhooks you already created are listed here. An
empty workspace shows **No Web Hook Created** with the text "Add your first
endpoint to receive event notifications."

### Step 2: Create the webhook

Select **Create Webhook** and fill in the dialog.

| Field            | What to enter                                                                                                                                                                                                     |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Name**         | Required. 1 to 100 characters. The placeholder suggests "e.g. Production Webhook".                                                                                                                                |
| **Endpoint URL** | Required. A full URL with its scheme, such as the placeholder "https://your-domain.com/webhook". The dialog accepts `https` only and shows "Enter a valid URL that starts with https://." until the value parses. |
| **Event Types**  | Tick at least one of **Job completed**, **Job failed**, **Spending warning**, **Spending exhausted**. The API rejects an empty selection.                                                                         |
| **Enable**       | On by default and shown as **Enabled**. Turn it off to create the webhook as **Disabled**.                                                                                                                        |

Select **Create**. The dialog closes and the new webhook appears at the top of
the list.

> [!NOTE]
> The **Create** button is enabled as soon as **Name** and **Endpoint URL** are
> valid. If you left every event unticked, the request fails and the dialog
> shows `subscribedEvents must contain at least 1 elements`. Tick an event and
> try again.

### Step 3: Save the signing secret

Every new webhook gets a signing secret. It starts with `whsec_`. After you
select **Create**, the dialog shows the secret once, with a **Copy** button
and a red warning. Select **Copy** and store the secret. Select **Done** to
close the dialog. The secret cannot be shown again. The create response also
returns it once, in the `signingSecret` field. From then on the portal and
every API response carry only its first 12 characters, as
`signingSecretPrefix`.

> [!WARNING]
> The secret is shown once and cannot be recovered. Store it in your secret
> manager, never in source control, and use it only on the server that
> receives deliveries. If you lose it, delete the webhook and create a new one.

### Step 4: Send a test event

To confirm the endpoint works, start an asynchronous job with an API key and
let it finish:

:::tabs

```bash title="cURL"
curl -X POST https://api.echonova.ai/v1/text-to-speech \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "Webhook test", "voiceId": "VOICE_ID"}'
```

```python
import requests

response = requests.post(
    "https://api.echonova.ai/v1/text-to-speech",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={"text": "Webhook test", "voiceId": "VOICE_ID"},
)
print(response.json())
```

```javascript
const response = await fetch('https://api.echonova.ai/v1/text-to-speech', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ text: 'Webhook test', voiceId: 'VOICE_ID' }),
});
console.log(await response.json());
```

:::

When the job completes, every enabled webhook subscribed to `job.completed`
receives one `POST`. The request and body formats are on
[Webhook events](/docs/developers/webhooks-events), and the response your
endpoint must return is on
[Delivery, retries and failures](/docs/developers/webhooks-retries).

## Get the endpoint right

Answer fast and over HTTPS. A delivery waits 10 seconds for your response,
and the signature travels in a header, so a slow endpoint costs you retries.
Plain-HTTP endpoints are not accepted.

**Recommended**

- `https://api.example.com/echonova/webhook`, which verifies the signature,
  returns `200` at once, and processes the event afterwards.

**Less effective**

- `https://203.0.113.10:8443/hook`, which downloads the audio and writes to
  the database before it answers.

## What happens next

- The list shows each webhook's **Name**, **Subscribed Events**, a masked
  **ID**, the endpoint and its **Status** badge, **Active** or **Disabled**.
- The switch on the row enables or disables the webhook. A toast confirms
  **Updated** with "Webhook enabled" or "Webhook disabled". A disabled webhook
  receives nothing until you enable it again.
- The row menu offers **Edit**, **Copy webhook ID** and **Delete**.
- **Edit** opens the same dialog titled **Edit Webhook**, with the button
  **Save Changes**. You can change the name, the endpoint URL, the events and
  the enabled state. The signing secret does not change.
- **Delete** opens the **Delete webhook** dialog, which asks
  `Delete the webhook "NAME"? This cannot be undone.` with your webhook's
  name in place of `NAME`. Confirm with **Delete**. The webhook stops
  receiving events at once.
- If 5 deliveries in a row end in failure, the webhook is disabled
  automatically. Fix the endpoint, then turn it back on; enabling resets the
  failure count.

## Limits

| Limit                                             | Value                                                    | Applies to      |
| ------------------------------------------------- | -------------------------------------------------------- | --------------- |
| Name length                                       | 1 to 100 characters                                      | Create and Edit |
| Endpoint URL                                      | Must include the scheme; the portal accepts `https` only | Create and Edit |
| Subscribed events                                 | At least 1 of the 4 event types                          | Create and Edit |
| Consecutive failed deliveries before auto-disable | 5                                                        | Each webhook    |
| Delivery timeout                                  | 10 seconds per attempt                                   | Each delivery   |
| Webhooks per page                                 | 20                                                       | The list        |

## Troubleshooting

### Enter a valid URL that starts with https://.

The dialog shows this while **Endpoint URL** does not parse as a URL with an
`https` scheme. Add the scheme, for example `https://`. Plain `http` is refused.

### subscribedEvents must contain at least 1 elements

You selected **Create** with no **Event Types** ticked. Tick at least one and
try again.

### endpointUrl must be a URL address

The API rejected the endpoint. It needs a scheme and a host. Check for spaces
or a missing `://`.

### Failed to create webhook. Please try again.

The portal could not read an error message from the API. Reload the page and
retry. If it persists, check that your role still has `developer_api.manage`.

### Webhook not found

The webhook was deleted, or it belongs to another workspace. Refresh the list.

### The webhook switched to Disabled on its own

5 deliveries in a row failed after all retries. Make the endpoint return a
`2xx` status within 10 seconds, then enable the webhook again. See
[Delivery, retries and failures](/docs/developers/webhooks-retries).

## FAQ

### Can I see the signing secret again?

No. It is returned once, in the create response. Delete the webhook and
create a new one to get a new secret.

### Does editing the endpoint URL change the secret?

No. The secret is generated once at creation and stays the same through every
edit, including changes to the URL, the events and the enabled state.

### Do sync calls trigger job events?

No. `job.completed` and `job.failed` are sent for asynchronous jobs only, and
only for jobs started with an API key. Jobs started from the portal are not
announced.

### Are webhooks per API key or per workspace?

Per workspace. Every enabled webhook in the workspace that subscribes to an
event receives it, whichever API key produced the job.

### Who can manage webhooks?

The workspace owner, whose role has `developer_api.manage`.

## Related

- [Webhook events](/docs/developers/webhooks-events)
- [Verify a webhook signature](/docs/developers/webhooks-verify-signature)
- [Delivery, retries and failures](/docs/developers/webhooks-retries)
- [Get an API key](/docs/developers/get-an-api-key)
- [Generate speech from text](/docs/reference/tts-generate-async)
