- Developers
- Webhooks
Webhooks
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.
What this flow solves
Section titled “What this flow solves”Ferry delivers webhooks to the endpoint addresses configured by your organization. However, the dispatch mechanism exhibits three core characteristics that dictate your integration design:
- No cryptographic signature: No HMAC, no shared secret, no timestamp, and no nonce. You cannot verify request authenticity from the payload itself.
- No retry mechanism: If dispatch fails or times out, the server logs the error and moves on. The event is lost permanently.
- 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.
Before you start
Section titled “Before you start”| Requirement | Why | Where to read |
|---|---|---|
| An API key with organization settings permissions | Used to configure endpoints programmatically | Authentication |
| A public HTTPS endpoint answering in under 2 seconds | The hard timeout limit is 2,000 ms | The “Delivery mechanics” section below |
| An internal processing queue on your side | Acknowledge with 200 immediately, process later | The “Safe handling pattern” section below |
| A scheduled reconciliation background job | Backfills any dropped or lost events | The “Safe handling pattern” section below |
| A list of event types to register | Target only the events actually emitted | The “Event names” section below |
Flow diagram
Section titled “Flow diagram”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" endStep-by-step
Section titled “Step-by-step”| Step | What your system does | What Ferry does | Success signal | Common failures |
|---|---|---|---|---|
| 1 | Deploy a public HTTPS receiving endpoint with an unguessable path | — | Endpoint returns 200 on test call | Using predictable paths such as /webhook |
| 2 | Return HTTP 200 immediately, enqueuing payload for asynchronous handling | — | Response time consistently under 2 seconds even under load | Synchronous processing causing timeout |
| 3 | Configure endpoint URL and event subscriptions in organization settings | Saves configuration per integration channel | Verification query shows new endpoint | Hesitating to update for fear of overriding settings |
| 4 | Execute a test transaction to trigger and verify event dispatch | Emits corresponding event | Endpoint receives event payload | Assuming registration works without testing |
| 5 | Deduplicate by resource ID and query API for authoritative state | Returns authoritative resource payload | Local record matches API data | Storing raw webhook payload directly |
| 6 | Enable periodic reconciliation background jobs | — | Job detects and corrects discrepancies | Skipping reconciliation entirely |
| 7 | Monitor event stream for abnormal silence | — | Alerts fire when no events arrive over expected intervals | Treating silence as normal operation |
States
Section titled “States”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.
Guarantees and limits
Section titled “Guarantees and limits”Where configuration lives
Section titled “Where configuration lives”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
POSTrequests. - 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”| Item | Reality | What you must do |
|---|---|---|
| HTTP Method | Always POST to the configured URL | Your endpoint must accept POST requests |
| Event Label | Provided in the x-event-type HTTP header | Inspect this header to route handling internally |
| Signature | None. No HMAC, no shared secret, no timestamp | Treat webhooks as signals, call the API for ground truth. Use unguessable URLs and IP filtering if possible |
| Timeout | 2,000 milliseconds hard cap | Accept payload, push to queue, return 200 immediately |
| Retries | None. Server errors are logged and discarded | Run scheduled reconciliation tasks |
| Delivery Ordering | Not guaranteed. Events dispatch independently | Do not base state transitions solely on arrival order |
| In-flight queue | No retry queue maintained by Ferry | An undelivered event is permanently lost |
Event names
Section titled “Event names”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
Registerable does not mean it fires
Section titled “Registerable does not mean it fires”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 type | Receiving organization |
|---|---|
| Booking events | The organization owning the booking |
| Order ticketing events | The ordering organization, plus an event for each parent organization in the tier chain (with NET prices recalculated for each level) |
| Child organization created events | The 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 and syntax risks
Section titled “Filter conditions and syntax risks”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 numeric1. - Dot notation for nested field paths (
order.code). - Boolean combination using
and,or, andnot(wherenotapplies 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.
Safe handling pattern on the partner side
Section titled “Safe handling pattern on the partner side”- Respond in under 2 seconds: Ingest payload, push to internal queue, return HTTP 200 immediately. Process business logic asynchronously.
- Deduplicate by resource ID: Combine resource identifier (booking, order, or request ID) with event type as a deduplication key.
- Treat webhooks as signals: Extract the resource identifier and query the API for authoritative data.
- Run scheduled reconciliation: Query recently updated records periodically to backfill dropped events.
- Monitor for abnormal silence: Trigger alerts when no events arrive over an unexpectedly long time span.
Common failures and how to handle them
Section titled “Common failures and how to handle them”| Symptom | Cause | What to do | Should you retry |
|---|---|---|---|
| No events received after subscription | Event type has no active platform emitter | Verify via real action; choose alternative event | Not applicable |
| Events received initially then stopped completely | Invalid filter syntax broke delivery loop | Remove or correct filter condition, verify with test action | Not applicable |
| Event payload contains outdated data | Delivery order is not guaranteed | Always query API for current authoritative state | Not applicable |
| Events lost during high traffic load | Endpoint took longer than 2 seconds, Ferry timed out | Switch to receive-then-enqueue pattern; backfill with polling | No — dropped events are not retried |
| Received duplicate events | Webhooks carry no deduplication key | Deduplicate by resource ID and event type | Not applicable |
| Received ticketing events for unfamiliar orders | Event was dispatched to parent tier organization | Route child organization ticketing events separately | Not applicable |
| Updating settings risks overwriting other config | Configuration merges per channel | Read back settings to verify | Not applicable |
Where to look in Scalar
Section titled “Where to look in Scalar”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 goal | Group in API reference |
|---|---|
| Update organization settings (configure webhook URL and events) | Organizations |
| Read organization settings to verify configuration | Organizations |
| Booking event data structures | Bookings |
| Order event data structures | Orders |
| Cancellation request event data structures | Cancel Ticket Requests |
| Customer event data structures | Customers |
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.
Read next
Section titled “Read next”| Page | When to read |
|---|---|
| Notification settings | Configure webhook endpoints visually in the Dashboard |
| Booking: the life cycle of one booking | Understand why API polling serves as the ground truth for booking status |
| Ticket issuance and orders | Differentiate between parent and child organization ticketing events |
| Cancellation and refunds | Review event types required for ticket cancellation workflows |
| Money: balance, credit limit and debt | Reconcile payment events with transaction ledgers |
| Conventions | Review shared data conventions and deduplication rules |
Open questions
Section titled “Open questions”| Question | Status |
|---|---|
| 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 closed | Awaiting product decisions; partners receive event notifications but cannot read invoice payloads directly via API. |