POST when payment starts, a booking completes or fails, or a cancellation or exchange reaches an outcome.
How it works
- Register a public HTTPS endpoint and choose the events you need.
- Jinko sends a signed event when the booking changes.
- Verify the signature and deduplicate the event.
- Return
2xx, then process the event in your application.
data.customer_emails. See Send your own customer emails to use that data with your own brand.
Prerequisites
- A Jinko account
- A public HTTPS endpoint that can receive a
POST
Tenant subscriptions
A booking made with a tenant key sends events to the subscriptions registered for that tenant. The payload, headers, and signature are the same for every authentication mode. Register the endpoint in the dashboard, select the tenant, and choose the events. Subscriptions are environment-specific, so register them separately in production and sandbox. For sandbox bookings, turn on Sandbox mode in the dashboard before creating the subscription.Register an endpoint
- Dashboard
- API
Open Webhooks in the dashboard, select Add webhook, paste your URL, choose the events, and select Create. Copy the signing secret shown once. You need it to verify deliveries.
Choose events
Seat confirmation: Jinko sends
booking.completed and the flight booking confirmation email once the flight is ticketed, even if a seat is still pending. Read booked_ancillaries for each seat’s outcome. See Seat maps.
Exactly one of booking.completed, booking.partial and booking.failed fires per trip. They are mutually exclusive. booking.processing reports an earlier moment: the payment is authorized and the booking is being confirmed. The servicing.* events fire after the booking, once for each operation, and only if you subscribe to them.
More event types will be added over time. Treat the event field as an open enum and ignore events you don’t handle.
No event reports a change the airline makes after ticketing, such as a schedule change or a cancellation. Jinko doesn’t detect those today, and get_booking returns the itinerary as it was booked.
Every event in the table is in the WebhookEvent enum of the production OpenAPI contract.
Read the payload
Every event carries these six fields. The example leaves outdata, described below:
status is deprecated: branch on event, which already names the outcome. Events also carry a data object with the facts of the booking or the operation and, when Jinko emails your traveler at that moment, that email:
Fetch the full booking with
get_booking using the booking_ref.
Customer emails
Every event exceptbooking.partial carries data.customer_emails: one entry for each email Jinko sends, or would send, your traveler at that moment, with what the email shows. Use it to send the email in your own brand. Send your own customer emails explains how, and how to ask Jinko to stop sending its own.
The example shows only some fields of each
content. Each template’s content has its own schema in the OpenAPI contract, named after the template: for example FlightTicketingConfirmationEmailContent. A later version can add fields to content and entries to customer_emails, so ignore what you don’t use.
Completed bookings
booking.completed lists every booked item in data.items, so you can tell the traveler the airline or hotel reference without calling get_booking.
data.items is absent on events sent before contract 0.75.0.
Partial bookings
booking.partial carries data describing what actually happened per item and how much was captured.
The
*_money fields use the platform’s one money shape: value is an integer in minor units, divide it by 10 ** decimal_places, and display is the same figure ready to show. See Money & prices. The deprecated captured_amount and captured_total are still sent, in major units, so do not divide those.
Failed bookings
booking.failed carries failure_reason when the failure has a classified cause: price_changed, offer_unavailable, booking_not_created or booking_cancelled_at_provider. failure_message, a customer-facing sentence, is sent for the first two causes only. Both are absent when the failure has no classified cause. customer_emails carries the booking-failed email.
Cancellations
servicing.completed reports a post-booking operation on one booked item, today a cancellation, that succeeded and whose money was settled. It is a separate event from booking.completed because it reports a booking being undone. Its data names the operation and the money, and carries the cancellation email in customer_emails (left out of this example):
refund and penalty use the same money shape as the *_money fields above, with decimal_places at the ISO 4217 digits of the currency. null means the operation carries no such figure, which is different from a figure of zero.
Exchanges and failed operations
servicing.exchange_confirmed, servicing.ticket_issued and servicing.failed each report one operation on one booked item. Their data carries:
The
event_id is an opaque string, unique to each occurrence of an event. Its format is not part of the contract: don’t parse it. The four servicing.* events can happen more than once for the same booking, for example servicing.completed for each cancelled item, and each occurrence has its own event_id. Retries and replays reuse it, so your dedupe on X-Jinko-Event-Id covers them.Verify the signature
ComputeHMAC-SHA256(secret, "<X-Jinko-Timestamp>.<raw request body>") and compare it, in constant time, to the hex in X-Jinko-Signature (after the sha256= prefix). Use the raw request body: parsing and re-serializing the JSON will change the bytes and break the check.
- Node.js
- Python
X-Jinko-Timestamp is outside your tolerance in either direction (the examples use 5 minutes) to guard against replays. Respond 2xx once you’ve accepted the event.
Handle retries and duplicates
- Return
2xxas soon as you have stored the event. Process slower work asynchronously. - Jinko retries a timeout or non-
2xxresponse with exponential backoff, up to 8 attempts over about 80 minutes. - Deduplicate retries with
X-Jinko-Event-Id. Every attempt for the same business event uses the same value. - After the eighth failed attempt, the delivery becomes
exhausted. You can replay it from the delivery log.
Inspect and replay deliveries
Jinko records every attempt to deliver an event.- Dashboard: open Webhooks in the dashboard, select the webhook, then open Deliveries. You can inspect each attempt and replay a delivery.
- API with a legacy person-bound key:
GET /v1/webhooks/{id}/deliverieslists deliveries.POST /v1/webhooks/{id}/deliveries/{delivery_id}/replayreplays one.
A replay sends the same event again: same
event_id, same payload, signed with a new X-Jinko-Timestamp. It starts a fresh set of attempts and adds one to the delivery’s replay_count. Replay is accepted for a delivered or exhausted delivery. It is refused with 409 and code delivery_in_progress while the delivery is pending or failed, and with 409 and code payload_purged once the payload is deleted, 30 days after the delivery’s last attempt or replay.
Because a replay keeps the event_id, a receiver that deduplicates on X-Jinko-Event-Id treats it as an event it already has, if it processed the first delivery.
Test the endpoint
The dashboard has no test button. With a legacy person-bound key, send a test event through the API:"livemode": false and booking_ref: "JNK-TEST00", so you can confirm your signature handling end-to-end without a real booking.