Destinations
A destination is a URL of yours that receives a source’s webhooks, together with how to authenticate to it. Every incoming event creates one forward for each enabled destination of its source. Each destination has its own retries, timeout and credentials, so a slow service does not hold up the others.
Add a destination
Section titled “Add a destination”const destination = await client.incomingSources.createDestination(projectId, sourceId, { url: 'https://billing.internal.example.com/webhooks/stripe', enabled: true, maxAttempts: 5, timeoutSeconds: 30,});from railhook import IncomingDestinationCreateParams
destination = client.incoming_sources.create_destination( project_id, source_id, IncomingDestinationCreateParams( url="https://billing.internal.example.com/webhooks/stripe", enabled=True, max_attempts=5, timeout_seconds=30, ),)<?php$destination = $client->incomingSources->createDestination($projectId, $sourceId, [ 'url' => 'https://billing.internal.example.com/webhooks/stripe', 'enabled' => true, 'maxAttempts' => 5, 'timeoutSeconds' => 30,]);curl -X POST "$RAILHOOK_URL/api/v1/projects/$PROJECT_ID/incoming-sources/$SOURCE_ID/destinations" \ -H "X-API-Key: $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://billing.internal.example.com/webhooks/stripe", "authType": "BEARER", "authConfig": "{\"token\":\"'"$BILLING_TOKEN"'\"}", "timeoutSeconds": 30 }'| Field | What it does |
|---|---|
url |
Required. Where forwards are sent. Checked against private address ranges like an endpoint URL, see Endpoint security. |
enabled |
Whether the destination receives forwards. |
authType, authConfig |
How Railhook authenticates, see below. authConfig is a JSON string, up to 4096 characters, encrypted at rest. |
customHeadersJson |
Extra HTTP headers, as a JSON object in a string. |
maxAttempts |
Attempts before the forward is abandoned. Default 5. |
retryDelays |
Comma-separated waits in seconds. Default 60,300,900,3600,21600. |
retryableStatuses |
Which HTTP statuses are worth another attempt. Default 408,429,500-599. See Which statuses are worth retrying. |
autoDisabledAt |
Read-only. Set when Railhook turned this destination off after a window of nothing but failures. See When Railhook turns an endpoint off; a destination is turned off on the same terms. |
timeoutSeconds |
How long one attempt waits for a response. Default 30. |
transformationId |
A saved transformation applied before forwarding. |
payloadTransform |
A JSONPath expression, such as $.data, used when no transformationId is set. |
Authentication
Section titled “Authentication”Forwards are not signed. Railhook proves who it is to your service with the destination’s credentials:
authType |
authConfig |
Sent as |
|---|---|---|
NONE |
none | Nothing. |
BEARER |
{"token": "…"} |
Authorization: Bearer <token> |
BASIC |
{"username": "…", "password": "…"} |
Authorization: Basic <base64> |
CUSTOM_HEADER |
{"headerName": "…", "headerValue": "…"} |
The named header |
Credentials are masked in the request headers the dashboard shows.
What the destination receives
Section titled “What the destination receives”The body is the webhook exactly as the provider sent it, byte for byte, with its original Content-Type and, when it had one, Content-Encoding, unless a transformation is configured. A transformed body is sent as UTF-8. Railhook adds these headers:
| Header | Value |
|---|---|
X-Incoming-Event-Id |
The incoming event. |
X-Incoming-Request-Id |
The request id the ingress returned to the provider. |
X-Forward-Attempt |
The attempt number, starting at 1. |
Idempotency-Key |
<incoming event id>-<destination id>, stable across attempts. |
Of the provider’s own headers, only those that describe the event are passed through, transformed or not, because some providers name the event nowhere else: a GitHub push and a GitHub issue cannot be told apart by their bodies.
| Provider | Headers passed through |
|---|---|
| GitHub | X-GitHub-Event, X-GitHub-Delivery, X-GitHub-Hook-ID, X-GitHub-Hook-Installation-Target-Type, X-GitHub-Hook-Installation-Target-ID |
| GitLab | X-Gitlab-Event, X-Gitlab-Event-UUID, X-Gitlab-Instance |
| Shopify | X-Shopify-Topic, X-Shopify-Webhook-Id, X-Shopify-Shop-Domain, X-Shopify-API-Version, X-Shopify-Event-Id, X-Shopify-Triggered-At |
A header of the same name set by Railhook, the destination’s credentials or its custom headers takes precedence. Every other original header is not forwarded, and signatures and tokens never are (X-Hub-Signature-256, X-Shopify-Hmac-Sha256, X-Gitlab-Token, Stripe-Signature and the like). Verification happened at the ingress; your service should rely on the destination credentials instead.
Transforming before forwarding
Section titled “Transforming before forwarding”Railhook uses the first of these that is set: the saved transformation transformationId, then the JSONPath expression payloadTransform, then the body unchanged. If a configured transformation cannot be applied, the attempt fails and is retried; the untransformed body is never forwarded in its place. See Transformations.
When a destination does not answer
Section titled “When a destination does not answer”Forwards follow the incoming retry ladder: 5 attempts with waits of 1 min, 5 min, 15 min and 1 h, jittered between 50% and 150%. 408, 429, any 5xx, a connection error, a timeout and a destination host that does not resolve are retried; any other 4xx or a 3xx moves the forward to Failed Forwards at once, without the rest of the ladder, since it will not change until someone fixes the destination. Redirects are not followed. A forward still outstanding 24 hours after it started is abandoned whatever its attempt count. A retry from Failed Messages or a replay starts a new forward, so its 24 hours count from the retry or replay, not from when the webhook arrived.
Abandoned forwards appear under Failed Forwards. Retrying one creates a new forward to the same destination, starting again from attempt 1. See Retries and failed messages.