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:- Read the request body as raw bytes.
- Verify the timestamp and signature.
- Atomically store the event and mark it for processing, using its
idto prevent duplicate records. - Return a
2xxstatus after durable storage. - Read the related resource outside the request path.
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.
Why signature verification fails
Why signature verification fails
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: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 eventid. 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.