Webhooks
A webhook is an HTTPS request the panel sends to a URL of yours when something happens in your account: a backup finishes, a domain is added, a certificate is issued, somebody signs in. Your CI, your monitoring, or your own application receives it and acts on it. Nothing polls.
Webhooks are on the Integrations page (Integrations → Webhooks). Each URL you add there is an endpoint.
Every request has one format, Standard Webhooks, and a signature, so your receiver can prove the panel sent it. Receive a webhook shows the request, the code that checks the signature, and small relays to chat tools.
The panel sends Standard Webhooks and nothing else: no Slack, Discord, or PagerDuty requests. If a system needs another format, put a small relay between the panel and that system; Receive a webhook has examples.
Add an endpoint
| Setting | What it does |
|---|---|
| Name | Tells you which receiver this is. |
| URL | The address that gets the requests. Must use https. |
| Event types | The filter. The endpoint gets only matching events. |
| Secret header | Optional. One header with a secret value, e.g. Authorization: Bearer …, sent on every request. |
When you save the endpoint, the panel shows its signing secret, which
looks like whsec_…. Give it to your receiver. Later, the panel shows
the secret again only after you confirm your identity, and records that
in your account activity.
Many receivers put a credential in the URL, so once saved, the panel
shows only its start, e.g. https://hooks.example.com/…. To change the
URL, type the full new one.
The panel refuses a URL that points to a private network or to the server itself. An account can have one endpoint per URL.
Event type filters
A filter is a list of items like these:
| Filter item | Matches |
|---|---|
backup.failed | That event type only. |
backup.* | Every event type starting with backup.. |
* | Every event type. |
A filter can name an event type from a later version of the panel; the endpoint gets those events once the panel starts sending them.
Event types
The Webhooks page lists every event type your endpoints can get, as does
GET /api/v1/event-types.
| Event types | When |
|---|---|
domain.created, domain.removed | A hostname is added to or removed from your account. |
certificate.issued | A certificate authority issued a certificate, and the panel put it in use. |
certificate.refused | The panel can't get a certificate for a hostname until something changes. |
certificate.expiring | A certificate in use has 30 days left, then 7, and nothing renews it: one you uploaded, or one the panel couldn't renew. Once per certificate for each. |
dns.publication_failed | The name server refused part of a DNS zone for some time, so the internet sees different records from the panel. |
quota.threshold_crossed | Your account's storage reached 80 %, 95 %, or 100 % of its quota. Once for each, and again only after the use drops 5 points below it. |
backup.succeeded, backup.failed | A backup finished, failed, or was refused before it started. |
restore.succeeded, restore.failed | A restore finished, failed, or was refused before it started. |
webapp.action.succeeded, webapp.action.failed | A job on an installed web application finished or failed: an install, an upgrade, a repair. |
webapp.vulnerable | A security advisory affects an installed web application, or a package it uses. |
toolchain.task.succeeded, toolchain.task.failed | A job on a runtime finished or failed, e.g. a Perl module install. |
apikey.created, apikey.revoked | Somebody created or revoked an API key of your account. |
security.signed_in | Somebody signed in to your account with a password or a passkey. |
security.sign_in_denied | A sign-in gave the correct password, and one of your devices refused it. |
security.password_changed, security.password_breached | Your password changed, or the panel found it in a public list of leaked passwords. |
security.totp_enabled, security.totp_disabled | An authenticator app was set up or removed. |
security.passkey_added, security.passkey_removed, security.device_added | A passkey or a device was added or removed. |
sign_in_help.requested, sign_in_help.resolved | One of your email users asked for help to sign in, or you answered that request. |
webhook.test | The test event, sent from an endpoint's page. No filter matches it. |
Your account's security.* events go to your endpoints only. The server
operator doesn't get them; the operator has the server logs.
The server operator can have endpoints too, and gets most events about your account, e.g. a backup or a new domain.
Delivery
Each event goes to every endpoint whose filter matches it. The receiver must answer within 10 seconds.
| Answer from the receiver | What the panel does |
|---|---|
Any 2xx | Delivered. |
410 Gone | Failed. The panel disables the endpoint immediately. |
429, 502, 503, 504 | Tried again later, obeying Retry-After up to one hour. |
A redirect (3xx) | Failed. The panel doesn't follow redirects. |
| Any other status, a timeout, or a connection error | Tried again later. |
The retries come after 1 minute, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours, and 10 hours. After eight attempts in about 28 hours, the delivery fails.
If an endpoint has had no success for five days and ten deliveries failed, the panel disables it and sends you one message. Once the receiver is fixed, enable the endpoint on its page.
Each event has an id, the webhook-id header, which is the same on every
attempt and redelivery. Events can arrive twice and out of order, so your
receiver must use the id to ignore a second copy.
Delivery log and redelivery
An endpoint's page shows its deliveries, each with its attempts, status codes, and part of each answer. From there you can:
- send a test event, which goes out immediately, with the answer on the page;
- redeliver one delivery;
- redeliver the failed deliveries since a time you choose, once per 10 minutes.
The delivery log is kept for 30 days.
Rotate the signing secret
Rotate makes a new signing secret. For the next 24 hours, every request carries two signatures, one with each secret; put the new secret in your receiver during that time.
Limits
| Limit | Default | At the limit |
|---|---|---|
| Endpoints | 10 | A new endpoint is refused. |
| Deliveries | 60 per minute, 2,000 per hour | Deliveries wait; none are dropped. |
| Waiting deliveries | 5,000 | New deliveries are skipped, and you're told once a day. |
| Size of a request | 256 KiB |
The server operator can change the first two limits for your account. The Webhooks page shows your usage, and when deliveries are waiting or were skipped.
API and MCP
The REST API has the same operations as the page:
| Path | What |
|---|---|
/api/v1/webhook-endpoints | List, create, edit, and delete the endpoints. |
/api/v1/webhook-endpoints/{id}/actions | Pause, resume, or enable an endpoint. |
/api/v1/webhook-endpoints/{id}/tests | Send a test event. |
/api/v1/webhook-endpoints/{id}/secret-rotations | Rotate the signing secret. |
/api/v1/webhook-endpoints/{id}/deliveries | The delivery log. |
/api/v1/webhook-endpoints/{id}/retries | Redeliver the failed deliveries since a time. |
/api/v1/webhook-deliveries/{id}/retries | Redeliver one delivery. |
/api/v1/event-types | The event types, with the family of each. |
The MCP server has the webhook_* tools and
event-types_list. The API reference describes the body of each event
type in its webhooks section.
The API and MCP show a signing secret only when they create or rotate it, never again.
Reading the endpoints needs webhook:readonly; changing them needs
webhook:readwrite.
To subscribe an endpoint to a family of events, the key also needs read
access to that family; otherwise the API refuses with 403. Without
this, a narrow key could read through a webhook what its scope hides.
| Filter | Scope that the key also needs |
|---|---|
security.* | identity:readonly |
domain.*, dns.* | dns:readonly |
certificate.* | certificate:readonly |
webapp.* | webapplication:readonly |
quota.* | usage:readonly |
sign_in_help.* | email:readonly |
backup.*, restore.*, apikey.*, toolchain.*, and * | all:readonly |
webhook.* | No other scope |