Skip to main content
Webhooks tell your system when an order, tracking snapshot, file list, or inspection report changes. Each request uses CloudEvents 1.0 and the Standard Webhooks signature format. Use the event to start work, then read data.resource_url when that work needs the complete current resource.

Configure an endpoint

Open Customer API in the Bahn portal. Under Webhooks, select Add endpoint, choose the environment, and enter a publicly reachable HTTPS URL. Store the signing secret before closing the confirmation dialog. The portal shows it once. The receiver authenticates Bahn with the signing secret and signature headers. Incoming webhook requests do not use your OAuth access token.

Build the receiver

Your endpoint must follow this order:
  1. Read the request body as raw bytes.
  2. Verify the timestamp and signature.
  3. Atomically store the event and mark it for processing, using its id to prevent duplicate records.
  4. Return a 2xx status after durable storage.
  5. Read the related resource outside the request path.
Return a successful status for an already stored event. If storage fails, return a failure so Bahn can retry. Acknowledge after committing the event, not after merely starting an asynchronous task.
Verify the signature before you parse the body. A parse and serialize cycle changes the signed bytes.

Verify the signature

The request contains three signature headers. Remove the whsec_ prefix from the secret, then decode the remaining value as Base64. Join the webhook ID, timestamp, and raw body with full stops. Calculate HMAC-SHA256 and compare the Base64 result in constant time. This complete Node.js function returns the parsed event only after verification.
Use a maintained Standard Webhooks library when your language has one.
Check that the receiver uses the raw body, the correct environment secret, and a timestamp in seconds. Keep the whsec_ prefix in storage, but remove it before Base64 decoding.

Follow one event through the integration

A newly accepted order can produce this event:
Verify the raw request body, store this event durably, then return 204. A worker reads https://sandbox.api.bahnexpress.fi/v2/orders/ord_01k1a2b3c4d5e6f7g8h9j0k1m2 with a sandbox access token and updates your local order view. If acknowledgement is lost, Bahn can send the same event again. Your receiver finds the existing event ID and returns success. Your worker retries failed resource reads independently of webhook delivery.

Handle event ordering

Webhook delivery is at least once. Delivery order can differ from event order. Deduplicate by event id. Use data.order_sequence to order events for one order. It is not a sequence across all orders. Do not let a late event overwrite newer state or rebuild the order from event fields. Read the current resource instead. Use data.ordered_for to route an ordering-channel event to the right business. The value is null when the order belongs to your own account. Resolve data.resource_url and data.related_resource_urls against the origin in the event source. Order events point to the order. Tracking, file, and inspection events point to their related resources.

Event types

An item event also reports the resulting order summary change. Bahn does not send an additional order status event for the same transition. A cancellation event does not cause an additional order status event.

ETA and tracking events

Bahn sends an ETA event when an ETA appears, disappears, changes by 15 minutes or more, or crosses a target boundary. Small ETA changes accumulate from the last notified ETA. Read the order before you send a customer message or store the new ETA. Bahn does not send a webhook for each position. Poll tracking while a map is open and use the ETag.

Retries and replay

Bahn sends up to eight retries after the first failed attempt. The delays grow from one minute to 24 hours. The customer portal shows delivery attempts and errors. It keeps completed delivery history for 30 days. You can replay a retained event only to the endpoint that received it. The replay keeps the event ID. Use a separate endpoint and signing secret for each environment.