Skip to main content

Webhooks

A webhook is an HTTPS request the server sends to a URL of yours when something happens: an account is created or suspended, a backup fails, a certificate is refused, an admin user signs in. The server's endpoints get the events about the server itself and the events about every account, so one receiver sees the whole box. Typical receivers are a billing system, a SIEM, and an on-call pager or a chat channel behind a relay.

The server's endpoints are on Integrations › Webhooks in the admin app, and in elpanctl as webhook:endpoint:* with --server. Account holders set up their own in the account panel; the account docs' Webhooks page describes those, and what is the same for both: the request format, the retry schedule, the filters, and rotation.

Every request is a Standard Webhooks POST signed with the endpoint's secret, and nothing else: a system that wants another format gets it from a relay you run. Receive a webhook shows the request, the code that verifies it, and relays to chat tools; it applies to the server's endpoints word for word. The server receives no webhooks; a system that wants to tell it something calls the Server API.

Add an endpoint​

elpanctl webhook:endpoint:create --server <name> <url> [--event=<type>]... [--secret-header="<Name>: <value>"] [--allow-private-network] [--json]

or Create Endpoint on the Webhooks page, which asks for the same:

SettingWhat it does
NameTells you which receiver this is.
URLThe address that gets the requests.
Event typesThe filter: types (backup.failed), families (backup.*) or *. --event defaults to *.
Secret headerOptional. One header with a secret value sent on every request, e.g. Authorization: Bearer …, for a receiver that checks a token rather than the signature.
Allow a private network addressLets the endpoint post to an internal system, such as an http://10.x chat bridge. Off by default.

The command prints the endpoint's signing secret, whsec_…; the page shows it once after the endpoint is made. Give it to the receiver.

The server's endpoints differ from an account's in two ways, because they are your own:

  • The URL may be plain http, and with Allow a private network address it may point into a private network. An account's must be https and public. The server's own addresses are refused either way: the panel does not post to itself.
  • There is no rate budget and no backlog cap. The server's deliveries are claimed before any account's, so a flood from one account does not delay your account.suspended. The one limit is 50 endpoints.
The URL is a secret

Many receivers put a credential in the URL, so the server stores and shows it like one: masked after saving, as https://hooks.example.net/…, and kept out of the logs. To change it, type the whole new one. The server allows one endpoint per URL.

Reading a secret again on the page asks you to confirm it's you and writes a row to the Auth Log. The API and the CLI show a secret when they create or rotate it, never again.

Event types​

The Webhooks page lists the types the server's endpoints can get, as does GET /api/v1/event-types, each with its family and a description. The server's endpoints get these:

Event typesWhen
account.created, account.updated, account.suspended, account.unsuspended, account.terminated, account.restored, account.removedAn account's life cycle. account.updated is a change to its system-level settings (its limits, locale, time zone, IP).
account.purge.succeeded, account.purge.failedA deletion finished, or stopped at a step. One per attempt.
apikey.created, apikey.revokedA server API key, or an account's key, was created or revoked.
domain.created, domain.removedA hostname was added to or removed from an account.
certificate.issued, certificate.refused, certificate.expiringA CA-signed certificate was put in use; the server can't get one for a hostname until something changes; a certificate in use has 30 days left, then 7, and nothing renews it.
dns.publication_failedA name server refused part of a zone, so the internet sees different records from the panel.
quota.threshold_crossedAn account's storage reached 80 %, 95 % or 100 % of its quota.
backup.succeeded, backup.failed, restore.succeeded, restore.failedA backup or a restore finished, failed, or was refused before it started.
webapp.action.succeeded, webapp.action.failedA job on an installed web application finished or failed: an install, an upgrade, a repair.
webapp.upgrade_run.finishedA mass upgrade started with webapp:upgrade finished (Upgrade Runs).
webapp.vulnerableA security advisory affects an installed web application.
security.signed_in, security.sign_in_denied, security.password_changed, security.password_breached, security.totp_enabled, security.totp_disabled, security.passkey_added, security.passkey_removed, security.device_addedAn admin user's sign-in and credential changes.
sign_in_help.requested, sign_in_help.resolvedAn account asked you for help to sign in, or the request was answered (Sign-in Help Requests).
webhook.testThe test event. No filter matches it.
What goes where

An event about an account goes to that account's endpoints and to the server's. An event about the server or an admin user goes to the server's only. The exceptions: an account's own security.* events go to the account only (you have the auth log and syslog), and a domain user's request for sign-in help goes to the account that owns the mailbox. toolchain.task.* events are the account's only.

A server endpoint's copy of an event links to the admin app and the server API, and its data.summary (a title, a sentence or two, a severity, a link to the admin app's page) is written in the server's default locale, so a relay that posts to a chat channel needs nothing else.

Delivery​

The receiver must answer within 10 seconds. A 2xx is delivered; a 410 Gone fails the delivery and disables the endpoint at once; 429, 502, 503 and 504 are tried again, obeying Retry-After up to one hour; a redirect is a failure, because redirects are not followed; anything else is tried again. The retries come after 1 minute, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours and 10 hours: eight attempts in about 28 hours, then the delivery fails.

An endpoint that fails for five days is disabled

With no success for five days and ten failed deliveries, the endpoint is disabled, and one message goes to the server's root mailbox (System mail). Once the receiver is fixed, enable the endpoint again; the form offers to redeliver what failed while it was disabled.

The webhook-id header is the same on every attempt and redelivery. Events can arrive twice and out of order, so a receiver uses it to ignore a second copy.

Pause, resume, enable, delete​

elpanctl webhook:endpoint:<action> --server <id>
ActionEffect
pauseNothing is queued for the endpoint while it is paused.
resumeDeliver to a paused endpoint again.
enableEnable a disabled (or paused) endpoint again, with a clean health record.
deleteWhat was still waiting for it is dropped; what was delivered stays in the log for 30 days.

webhook:endpoint:list --server [--format=tsv|jsonl] gives the ids.

Test, redeliver, rotate​

elpanctl webhook:endpoint:test --server <id> [--queue [--count=<n>]]
elpanctl webhook:endpoint:redeliver --server <id> --delivery=<delivery id>
elpanctl webhook:endpoint:redeliver --server <id> --since="<time>"
elpanctl webhook:endpoint:rotate --server <id>
  • Test sends a webhook.test event at once and shows the attempt, so you see the receiver's answer. With --queue it goes through the delivery engine instead, like a real event; --count sends several. At most 5 tests a minute per endpoint.
  • Redeliver sends one delivery again now, or queues every failed or abandoned delivery whose event occurred since a time ("2026-10-29 10:00", "-6 hours"): abandoned means dropped while the endpoint was disabled. A bulk redelivery is allowed once per 10 minutes per endpoint.
  • Rotate gives the endpoint a new signing secret, and the old one keeps signing beside it for 24 hours, so the receiver can switch without a gap.

The endpoint's page has the same actions, and its delivery log: each delivery with its attempts, status codes, the headers that are safe to keep, and up to 1 KiB of the answer. The log is kept for 30 days.

API​

The Server API has every operation of the page, under the same paths as the account API but in plain JSON. Reading needs all:readonly, changing all:readwrite.

Method and pathWhat
GET, POST /api/v1/webhook-endpointsList the endpoints, oldest first; create one. The signing secret is in the POST response only.
GET, PATCH, DELETE /api/v1/webhook-endpoints/{id}One endpoint. PATCH changes the fields it carries; status: paused pauses, status: enabled resumes or enables again; secretHeader: null removes the header.
POST /api/v1/webhook-endpoints/{id}/testsSend a test event.
POST /api/v1/webhook-endpoints/{id}/secret-rotationsRotate the signing secret.
GET /api/v1/webhook-endpoints/{id}/deliveriesThe delivery log, newest first, 50 at a time: pass nextBefore back as before.
POST /api/v1/webhook-endpoints/{id}/retriesRedeliver the failed deliveries since a time.
GET /api/v1/webhook-deliveries/{id}, POST …/{id}/retriesOne delivery with its attempts; send it again.
GET /api/v1/event-typesThe catalogue, with each type's family and audiences.