Skip to content
Back to site

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).

  1. 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 secret and standardWebhooksSecret. Store them now: the API returns the plaintext secret only when an endpoint is created and when its secret is rotated.

  2. 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"}'
  3. Send an event (see Send an event below). Every endpoint subscribed to order.completed gets its own delivery.

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.

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.

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.

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.

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.

A destination is turned off on the same terms, and its queued forwards go to Failed Forwards.

Set ENDPOINT_AUTO_DISABLE_ENABLED=false in both the api and the worker.

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.

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.

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',
);

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.

The body is the event’s data object, not an envelope. Identifiers travel in headers:

A delivery
POST /webhooks HTTP/1.1
Content-Type: application/json
X-Signature: t=1738000000000,v1=<hex hmac-sha256>
X-Timestamp: 1738000000000
X-Event-Id: 6f0e…
X-Delivery-Id: 91ab…
X-Sequence-Number: 0
Idempotency-Key: order-12345-completed-<endpoint-id>
webhook-id: 91ab…
webhook-timestamp: 1738000000
webhook-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.