Skip to main content

Webhook setup

A webhook endpoint is a URL of yours that Epostix posts to as things happen to your mail. Nothing is polled and nothing is batched: each event is its own request.

Webhook endpoints, one in each health state

Adding an endpoint

Add endpoint asks for three things.

The URL has to start with http:// or https://. Private and internal addresses are refused, so localhost, 127.0.0.1, anything in 10.0.0.0/8, 172.16.0.0/12 or 192.168.0.0/16, and hostnames ending in .internal will not be accepted. Epostix has to reach your endpoint from the public internet, and a tunnel is the usual way to test against a machine on your desk.

The events are which of the eight event types this endpoint should receive. At least one is required. Subscribe to what you will act on rather than everything, because every event you subscribe to is a request your server has to answer.

A description is optional and up to 200 characters. It is what the list shows under the URL, and it is worth filling in the moment you have more than one endpoint.

A workspace can have up to 20 endpoints.

The signing secret

Each endpoint has a secret, shown in full exactly once when the endpoint is created. After that the screen shows only the whsec_ prefix. You can supply your own secret instead of letting Epostix generate one.

An endpoint, its secret and its delivery log

Verifying a request

Every delivery carries these headers:

HeaderValue
Webhook-IdA unique id for this delivery attempt
Webhook-TimestampWhen it was signed, in Unix seconds
Webhook-Signaturet=<timestamp>,v1=<signature>
Content-Typeapplication/json
User-AgentEpostix-Webhooks/1.0

The signature is an HMAC-SHA256, hex encoded, over the timestamp and the raw body joined by a dot:

signed_content = "{Webhook-Timestamp}.{raw request body}"
signature = hex(hmac_sha256(signed_content, secret))

Compare that against the v1= value with a constant-time comparison.

danger

Sign the raw body exactly as received. Parsing the JSON and re-serialising it changes whitespace and key order, and the signature will never match. Read the raw bytes first, verify, then parse.

Treat an unverified request as hostile. Your endpoint is a public URL, so anyone can post to it, and the signature is the only thing that says a request came from Epostix.

Rotating

Rotate issues a new secret and shows it once. The previous secret keeps validating for 24 hours, so both are accepted while you deploy the new one. Accept either during that window and drop the old one afterwards.

Rotate if the secret leaks, and rotate if you lost it, since there is no way to display it again.

Retries

A delivery counts as successful when your server answers with a 2xx status. Anything else, including a timeout or a connection failure, is a failure.

A failed delivery is retried up to five attempts in total:

AttemptWhen
1Immediately
2After 5 minutes
3After 30 minutes
4After 2 hours
5After 24 hours

After the fifth, that event stops being retried and is marked Failed.

note

The last gap is a day long, so an outage on your side that is fixed within a few hours may still see deliveries arrive long afterwards. Your handler needs to be idempotent: the same event can arrive more than once, and Webhook-Id plus the event id is how you recognise a repeat.

Health

The list shows one of four states per endpoint.

StateMeaning
HealthyNothing is failing
n failuresDeliveries are failing, and this many have failed since the first one
PausedYou turned it off. Nothing is sent
DisabledEpostix turned it off after 7 days of continuous failure

One success clears the count. A failing endpoint that answers correctly once goes back to Healthy, and the clock that leads to being disabled resets with it.

An endpoint that has been failing continuously for seven days is disabled automatically and says so on the screen. Turning it back on clears the failure history and starts it fresh, so fix the receiving end first.

Testing and replaying

Send a test event posts a webhook.test event to the endpoint immediately. It is the fastest way to confirm the URL, the signature check and your handler all work. This event type cannot be subscribed to; it only ever arrives because you asked for it.

The delivery log lists every attempt with the status your server returned, which attempt number it was, and when. Filter it by status or by event type. Open a delivery to see exactly what was sent and exactly what came back, which is usually enough to tell a signature bug from an application error.

Select deliveries to send them again. Redelivery is limited to 10 an hour per endpoint, which is enough to recover from a bad deploy and not enough to replay a week of traffic.