Skip to content

Webhooks

10 min read

Ferry sends outbound webhook events to partner receiving endpoints when business actions occur across the platform. The delivery mechanism is designed around rapid event signaling, requiring partners to acknowledge receipt immediately and query the API directly for authoritative data.

Ferry delivers webhooks to the endpoint addresses configured by your organization. However, the dispatch mechanism exhibits three core characteristics that dictate your integration design:

  1. No cryptographic signature: No HMAC, no shared secret, no timestamp, and no nonce. You cannot verify request authenticity from the payload itself.
  2. No retry mechanism: If dispatch fails or times out, the server logs the error and moves on. The event is lost permanently.
  3. Hard 2,000-millisecond deadline: If your endpoint takes longer than two seconds to respond, the connection is dropped and the event is never retransmitted.

These three characteristics lead to a consistent architecture rule: use webhooks as signals, use polling as the safety net. Webhooks notify you that an update occurred; your system then calls the API back to obtain the ground truth.

RequirementWhyWhere to read
An API key with organization settings permissionsUsed to configure endpoints programmaticallyAuthentication
A public HTTPS endpoint answering in under 2 secondsThe hard timeout limit is 2,000 msThe “Delivery mechanics” section below
An internal processing queue on your sideAcknowledge with 200 immediately, process laterThe “Safe handling pattern” section below
A scheduled reconciliation background jobBackfills any dropped or lost eventsThe “Safe handling pattern” section below
A list of event types to registerTarget only the events actually emittedThe “Event names” section below

The diagram below shows the path of an event from when it occurs inside Ferry to your receiving address:

flowchart TB
A["Business event inside Ferry"] --> B["System listener captures event"]
B --> C["Lookup organizations subscribed to event"]
C --> D{"Does organization have endpoint configured"}
D -->|No| Z["Stop, nothing sent"]
D -->|Yes| E["Iterate over registered endpoints"]
E --> F{"Does endpoint have filter condition"}
F -->|No| H["Send event"]
F -->|Yes| G{"Evaluate condition against data"}
G -->|No match| Y["Skip endpoint"]
G -->|Match| H
G -->|Syntax error in condition| X["Processing error<br/>may cause dropped events"]
H --> I["Send data to your endpoint<br/>2000 ms timeout<br/>header x-event-type<br/>no signature"]
I --> J{"Endpoint answers within 2 seconds"}
J -->|Yes| K["Webhook delivered successfully"]
J -->|No or error| L["Log on Ferry side<br/>no retry<br/>event lost permanently"]

The diagram below shows the safe handling pattern on the partner side:

sequenceDiagram
autonumber
participant P as "Your system"
participant Q as "Internal queue"
participant F as "Ferry API"
F--)P: "Webhook arrives with event-type header"
P->>Q: "Write payload to queue, defer business logic"
P-->>F: "Return HTTP 200 in under 2 seconds"
Q->>P: "Background worker dequeues payload"
P->>P: "Deduplicate by resource identifier"
P->>F: "Call API for authoritative resource data"
F-->>P: "Authoritative resource state"
P->>P: "Update system state based on API response"
alt No events received within expected window
P->>P: "Scheduled reconciliation job runs"
P->>F: "Query recently updated resources"
F-->>P: "Authoritative resource list"
P->>P: "Backfill missed events"
end
StepWhat your system doesWhat Ferry doesSuccess signalCommon failures
1Deploy a public HTTPS receiving endpoint with an unguessable path—Endpoint returns 200 on test callUsing predictable paths such as /webhook
2Return HTTP 200 immediately, enqueuing payload for asynchronous handling—Response time consistently under 2 seconds even under loadSynchronous processing causing timeout
3Configure endpoint URL and event subscriptions in organization settingsSaves configuration per integration channelVerification query shows new endpointHesitating to update for fear of overriding settings
4Execute a test transaction to trigger and verify event dispatchEmits corresponding eventEndpoint receives event payloadAssuming registration works without testing
5Deduplicate by resource ID and query API for authoritative stateReturns authoritative resource payloadLocal record matches API dataStoring raw webhook payload directly
6Enable periodic reconciliation background jobs—Job detects and corrects discrepanciesSkipping reconciliation entirely
7Monitor event stream for abnormal silence—Alerts fire when no events arrive over expected intervalsTreating silence as normal operation

Webhooks have no lifecycle states of their own. Each event is either delivered or dropped. The system does not maintain a delivery queue for retries and does not expose a delivery history log for inspection.

The authoritative state resides within the underlying business resources (bookings, orders, cancellation requests), retrieved by calling the corresponding API endpoints.

Webhook receiving endpoints are configured within the integration section of organization settings. Partners have two configuration paths:

  • Dashboard UI: Navigate to Notification settings and event integration to configure endpoints and select target event types.
  • Organization Settings API: Update configuration programmatically using your organization’s API key.

Each configuration entry consists of:

  • Receiving URL: A valid HTTPS URL accepting POST requests.
  • Event Subscriptions: The list of event codes your organization wants to receive.
  • Filter Conditions (optional): A filter expression evaluated prior to dispatch.

Updates to organization settings merge per integration channel, ensuring other organization configurations are preserved.

Delivery mechanics — the most important section of this page

Section titled “Delivery mechanics — the most important section of this page”
ItemRealityWhat you must do
HTTP MethodAlways POST to the configured URLYour endpoint must accept POST requests
Event LabelProvided in the x-event-type HTTP headerInspect this header to route handling internally
SignatureNone. No HMAC, no shared secret, no timestampTreat webhooks as signals, call the API for ground truth. Use unguessable URLs and IP filtering if possible
Timeout2,000 milliseconds hard capAccept payload, push to queue, return 200 immediately
RetriesNone. Server errors are logged and discardedRun scheduled reconciliation tasks
Delivery OrderingNot guaranteed. Events dispatch independentlyDo not base state transitions solely on arrival order
In-flight queueNo retry queue maintained by FerryAn undelivered event is permanently lost

The platform supports event types across several core groups:

  • Organization: organization.created, organization.updated, organization.disabled, organization.enabled, child-organization.created
  • Booking: booking.created, booking.updated, booking.issuedTicket, booking.issuedTicketError, booking.cancelOrder, booking.hold_payment, booking.canceled_payment
  • Order: order.created, order.updated, order.issuedTicket, order.childOrgIssuedTicket, order.childOrgIssuedTicketError, order.newManualIssueOrder, order.canceled, order.cancelTicket, order.forcedCanceled
  • Cancellation Request: cancel_ticket_request.created, .child_org_created, .updated, .accepted, .rejected, .accepted_refunded, .refunded, .recalled, .release_ticket
  • Customer: customer.created, customer.updated, customer.deleted
  • Company: company.created, company.updated, company.deleted
  • Lead: lead.created, lead.updated, lead.deleted
  • User: user.created, user.updated, user.deleted, user.created-with-profile
  • API Key: apiKey.created, apiKey.updated, apiKey.deleted
  • Voyage: voyage.created, voyage.created.manually, voyage.updated, voyage.disabled, voyage.has-order-voyage-disabled
  • Ticket Watchlist: ticketWatchlist.targetReached
  • Invoice: invoice.created, invoice.updated, invoice.published, invoice.failed, invoice.canceled, invoice.orphanReconcileAlert

The list of registerable event types is broader than the events actively triggered by platform listeners. Certain event codes exist in the dictionary but do not have active background emitters.

Mandatory verification step: after registering an event type, perform a concrete action in your test environment to confirm your endpoint receives the payload before building production business workflows.

Within the organization group, only child-organization.created actually emits a webhook. The four codes organization.created, organization.updated, organization.disabled, and organization.enabled can be registered but do not fire; do not rely on them as your only update source.

The receiver is not always the organization that produced the event

Section titled “The receiver is not always the organization that produced the event”
Event typeReceiving organization
Booking eventsThe organization owning the booking
Order ticketing eventsThe ordering organization, plus an event for each parent organization in the tier chain (with NET prices recalculated for each level)
Child organization created eventsThe direct parent organization, not the newly created organization

Multi-tier agencies must note: you receive two distinct ticketing event types — one for your own orders, and one for child organization orders. These carry different identifiers and price numbers, and should be processed by separate handlers.

Webhook data differs from data the API returns

Section titled “Webhook data differs from data the API returns”

Webhook payloads do not pass through role-based presentation serializers applied to standard API responses. Consequently, webhooks may include internal or extended fields omitted from normal API responses.

  • Security: Treat webhook payloads as sensitive data; avoid logging raw payloads or forwarding them indiscriminately.
  • Engineering: Do not build critical business logic dependent on fields present exclusively in webhooks and missing from standard API contracts.

Filter conditions allow filtering out unwanted event payloads. Key evaluation rules include:

  • Supports 6 basic comparison operators: equal, not equal, greater than, greater than or equal, less than, less than or equal.
  • Strict data typing: string '1' does not match numeric 1.
  • Dot notation for nested field paths (order.code).
  • Boolean combination using and, or, and not (where not applies only to the immediate first child branch).
  • Omitting filter conditions means all events of that type are delivered.

Webhook filters differ from dynamic API filters: webhooks support 6 comparison operators, while API filters support 13. Do not copy conditions unchanged between the two systems.

In the cancellation flow, malformed conditions can break the organization’s entire webhook delivery loop, rather than merely skipping nonmatching events. Unless filtering on Ferry’s side is essential, receive all events and filter within your system.

  1. Respond in under 2 seconds: Ingest payload, push to internal queue, return HTTP 200 immediately. Process business logic asynchronously.
  2. Deduplicate by resource ID: Combine resource identifier (booking, order, or request ID) with event type as a deduplication key.
  3. Treat webhooks as signals: Extract the resource identifier and query the API for authoritative data.
  4. Run scheduled reconciliation: Query recently updated records periodically to backfill dropped events.
  5. Monitor for abnormal silence: Trigger alerts when no events arrive over an unexpectedly long time span.
SymptomCauseWhat to doShould you retry
No events received after subscriptionEvent type has no active platform emitterVerify via real action; choose alternative eventNot applicable
Events received initially then stopped completelyInvalid filter syntax broke delivery loopRemove or correct filter condition, verify with test actionNot applicable
Event payload contains outdated dataDelivery order is not guaranteedAlways query API for current authoritative stateNot applicable
Events lost during high traffic loadEndpoint took longer than 2 seconds, Ferry timed outSwitch to receive-then-enqueue pattern; backfill with pollingNo — dropped events are not retried
Received duplicate eventsWebhooks carry no deduplication keyDeduplicate by resource ID and event typeNot applicable
Received ticketing events for unfamiliar ordersEvent was dispatched to parent tier organizationRoute child organization ticketing events separatelyNot applicable
Updating settings risks overwriting other configConfiguration merges per channelRead back settings to verifyNot applicable

This topic relates to the Organizations group on the API reference at https://api.ferry.vn/docs, which provides operations to inspect and update organization configurations:

Inspection goalGroup in API reference
Update organization settings (configure webhook URL and events)Organizations
Read organization settings to verify configurationOrganizations
Booking event data structuresBookings
Order event data structuresOrders
Cancellation request event data structuresCancel Ticket Requests
Customer event data structuresCustomers

The API reference specifies data schemas for each underlying resource. Timeout limits, lack of signatures, and absence of retries are documented exclusively on this guide page.

PageWhen to read
Notification settingsConfigure webhook endpoints visually in the Dashboard
Booking: the life cycle of one bookingUnderstand why API polling serves as the ground truth for booking status
Ticket issuance and ordersDifferentiate between parent and child organization ticketing events
Cancellation and refundsReview event types required for ticket cancellation workflows
Money: balance, credit limit and debtReconcile payment events with transaction ledgers
ConventionsReview shared data conventions and deduplication rules
QuestionStatus
Are there plans to add cryptographic signatures to webhooks?No current implementation planned; partners must use secret URLs and API verification.
Are there plans to introduce webhook retries?Not currently supported; partners must maintain periodic reconciliation background tasks.
Will official egress IP address ranges for webhooks be published?No published IP ranges yet; contact Ferry support if firewall rules are required.
Invoice events are emitted but partner invoice read APIs remain closedAwaiting product decisions; partners receive event notifications but cannot read invoice payloads directly via API.