Endpoints and subscriptions
An endpoint is a URL that receives your events. A subscription says which event types it gets. For each event you send, Railhook creates one delivery per endpoint with a matching subscription.
The dashboard’s Connections screen shows an endpoint with its subscriptions as one Connection. It is a view, not a separate record.
Examples use $RAILHOOK_URL (your Railhook API), $PROJECT_ID and $API_KEY (a project API key).
Create an endpoint and subscribe it
Section titled “Create an endpoint and subscribe it”-
Create the endpoint.
Terminal window curl -X POST "$RAILHOOK_URL/api/v1/projects/$PROJECT_ID/endpoints" \-H "X-API-Key: $API_KEY" \-H "Content-Type: application/json" \-d '{"url":"https://api.customer.com/webhooks","description":"Orders"}'The response carries
secretandstandardWebhooksSecret. Store them now: the API returns the plaintext secret only when an endpoint is created and when its secret is rotated. -
Subscribe the endpoint to an event type.
Terminal window curl -X POST "$RAILHOOK_URL/api/v1/projects/$PROJECT_ID/subscriptions" \-H "X-API-Key: $API_KEY" \-H "Content-Type: application/json" \-d '{"endpointId":"'"$ENDPOINT_ID"'","eventType":"order.completed"}' -
Send an event (see Send an event below). Every endpoint subscribed to
order.completedgets its own delivery.
Endpoint fields
Section titled “Endpoint fields”| Field | What it does |
|---|---|
url |
Required. Where deliveries are sent. Checked against private address ranges, see Endpoint security. |
description |
Free text, up to 500 characters. |
enabled |
Whether the endpoint receives deliveries. |
autoDisabledAt |
Read-only. Set when Railhook turned the endpoint off itself, see When Railhook turns an endpoint off. Absent when you turned it off. |
autoDisabledReason |
Read-only. Why, in words. |
failingSince |
Read-only. Start of the current unbroken run of failed deliveries; absent once one succeeds. |
signatureScheme |
BOTH (default), LEGACY or STANDARD: which signature headers are sent. See Verify signatures. |
rateLimitPerSecond |
Per-endpoint delivery throttle, 0 to 10000. 0 removes the limit. |
secret |
Optional. Supply your own signing secret instead of a generated one. |
allowedSourceIps |
A note field. It restricts nothing, see Endpoint security. |
When Railhook turns an endpoint off
Section titled “When Railhook turns an endpoint off”An endpoint that has been failing for days is turned off, and you are told. Otherwise every new event would start a fresh retry ladder against a dead receiver. Both conditions must hold:
| Condition | Default | Variable |
|---|---|---|
| Nothing has succeeded for this long | 72 hours | ENDPOINT_AUTO_DISABLE_AFTER_HOURS |
| …and the run contains at least this many failed attempts | 10 | ENDPOINT_AUTO_DISABLE_MIN_FAILURES |
The second keeps an endpoint that gets one event a week from being turned off for being idle. One success clears the run and the count.
The endpoint list and Connections show Auto-disabled and since when. The API returns autoDisabledAt, autoDisabledReason and failingSince on the endpoint.
What happens to deliveries already queued
Section titled “What happens to deliveries already queued”They go to Failed Messages with the reason recorded, so you can retry them once the receiver works again. See Retries and failed messages.
An endpoint you turn off is different: its queued deliveries end as FAILED, because you no longer want them delivered.
Turning it back on
Section titled “Turning it back on”Re-enabling clears the auto-disable and the run of failures, so the sweep does not turn it straight off again. Use the power button on the endpoint, or POST /api/v1/projects/{projectId}/endpoints/{id}/enable. Fix the receiver first: an endpoint that still fails starts a new run.
Who gets told
Section titled “Who gets told”An alert appears on the project’s Alerts page. With mail configured (EMAIL_ENABLED=true), the organization’s owners also get one email naming the endpoint, since when it has failed, and how to turn it back on. No alert rule is needed.
Incoming destinations
Section titled “Incoming destinations”A destination is turned off on the same terms, and its queued forwards go to Failed Forwards.
Turning the feature off
Section titled “Turning the feature off”Set ENDPOINT_AUTO_DISABLE_ENABLED=false in both the api and the worker.
Subscription fields
Section titled “Subscription fields”| Field | What it does |
|---|---|
endpointId |
Required. The endpoint that receives the matching events. |
eventType |
Required. An exact event type or a pattern, see below. |
enabled |
Whether the subscription creates deliveries. |
orderingEnabled |
Deliver this endpoint’s events in order. Off by default, see Ordering. |
maxAttempts |
1 to 20. How many attempts before the delivery is abandoned. |
retryableStatuses |
Which HTTP statuses are worth another attempt, e.g. 408,429,500-599 or 5xx,!501. See Which statuses are worth retrying. |
retryDelays |
Comma-separated waits in seconds between attempts, for example 60,300,900. |
timeoutSeconds |
1 to 60, default 30. How long one attempt waits for a response. |
transformationId |
A saved transformation to apply, see Transformations. |
payloadTemplate |
An inline template, used when no transformationId is set. |
customHeaders |
Extra HTTP headers, as a JSON object in a string. |
Leave out maxAttempts and retryDelays to get the default outgoing retry ladder: see Retries and failed messages.
Event type patterns
Section titled “Event type patterns”An event type is lower case, starts with a letter, and separates segments with dots: order.completed. A subscription can use a pattern instead:
| Pattern | Matches |
|---|---|
order.completed |
That event type only. |
order.* |
One segment after order.: order.completed, order.updated. Not order.line.added. |
order.** |
Any number of segments after order.: order.completed, order.line.added. |
* |
Any event type with a single segment. |
** |
Every event type in the project. |
Send an event
Section titled “Send an event”POST /api/v1/events with a type and a data object. It answers 201. Send an Idempotency-Key header so the receiver can recognise a repeat.
import { Railhook } from '@railhook/node';
const client = new Railhook({ apiKey: process.env.RAILHOOK_API_KEY, baseUrl: process.env.RAILHOOK_URL,});
const event = await client.events.send( { type: 'order.completed', data: { orderId: 'ord_12345', amount: 99.99 } }, 'order-12345-completed',);import os
from railhook import Railhook, Event
client = Railhook( api_key=os.environ["RAILHOOK_API_KEY"], base_url=os.environ["RAILHOOK_URL"],)
event = client.events.send( Event(type="order.completed", data={"order_id": "ord_12345", "amount": 99.99}), idempotency_key="order-12345-completed",)<?phpuse Railhook\Railhook;
$client = new Railhook( apiKey: getenv('RAILHOOK_API_KEY'), baseUrl: getenv('RAILHOOK_URL'),);
$event = $client->events->send( type: 'order.completed', data: ['orderId' => 'ord_12345', 'amount' => 99.99], idempotencyKey: 'order-12345-completed',);curl -X POST "$RAILHOOK_URL/api/v1/events" \ -H "X-API-Key: $API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: order-12345-completed" \ -d '{"type":"order.completed","data":{"orderId":"ord_12345","amount":99.99}}'Idempotency policy
Section titled “Idempotency policy”The project’s idempotencyPolicy decides what happens to an event sent without an Idempotency-Key:
| Policy | An event sent without a key |
|---|---|
NONE |
Is accepted, and no key is generated. |
AUTO |
Is accepted and gets a random key. |
REQUIRED |
Is rejected. |
Under any policy, a key the project has already used returns the original event instead of creating a second one.
What arrives at the endpoint
Section titled “What arrives at the endpoint”The body is the event’s data object, not an envelope. Identifiers travel in headers:
POST /webhooks HTTP/1.1Content-Type: application/jsonX-Signature: t=1738000000000,v1=<hex hmac-sha256>X-Timestamp: 1738000000000X-Event-Id: 6f0e…X-Delivery-Id: 91ab…X-Sequence-Number: 0Idempotency-Key: order-12345-completed-<endpoint-id>webhook-id: 91ab…webhook-timestamp: 1738000000webhook-signature: v1,<base64>
{"orderId":"ord_12345","amount":99.99}The event type is not in the body. Route on the payload or on the endpoint, or give the subscription a template that puts type into the body.
Idempotency-Key is the event’s key (or, without one, the event id), then - and the endpoint id. It is the same on every attempt of the delivery.