Webhooks push data out of TripWorks the moment something happens — a trip is booked, a payment lands, a waiver is signed. TripWorks sends an HTTP POST with a JSON body to any URL you choose, so you can wire bookings into a CRM, accounting tool, data warehouse, or a custom workflow without polling for changes.
In this article
Prefer no-code? The Zapier integration fires on the same events and connects to thousands of apps without writing anything. Webhooks are the raw-JSON path for your own systems — pick one path per event so a booking isn't processed twice downstream.
Learn how to manage permission groups
Add a webhook endpoint
- Go to Setup Tools Integrations, find the Webhooks card, and select Activate (or Configure if it's already on).
- Turn on Enable Webhooks — the master switch. Nothing is sent unless this is on.
- For each event you care about, paste your destination URL. Use + Add another endpoint to send the same event to more than one place.
Add a URL for each event you want. Preview the shape under Example JSON Payload. - Select Save. Webhooks take effect immediately.
Webhooks fire on real actions in your account — there's no separate test mode or "send test event" button. To try one out, point the URL at a free inspector like webhook.site (the Try It Now button links to it), then perform the real action.
Available events
The value in the payload's event field is the exact identifier below (an underscore slug — the setup page shows friendlier labels).
Event (event value) |
Fires when |
|---|---|
trip_created |
A new trip is created — including drafts and abandoned carts |
trip_reserved |
A trip moves to Reserved (typically the first payment) |
trip_updated |
A trip changes — date/time, guest count, custom fields, notes |
trip_cancelled |
A trip is cancelled |
booking_updated |
A guest booking within a trip changes — check-in, status, guest added/removed |
addon_updated |
An add-on or product is added to or removed from a booking |
payment_created |
Any payment or refund is processed (fires per payment, including partials) |
waiver_signed |
A customer signs a waiver |
payout |
A payout is sent to your bank account |
customer_create |
A new customer profile (with an email) is created |
customer_update |
A customer profile is modified |
The payload
Each webhook is a POST with Content-Type: application/json. There's no envelope — the object's fields sit at the top level with an event key merged in, like { "event": "trip_created", "id": …, "customer": {…}, "tripOrders": [ … ] }:
{ "event": "trip_created", "id": 2088161, "confirmation_code": "BOOK-1234" }
A few things to know when you parse it:
-
Money is in integer cents.
"total": 24200means $242.00. -
Trip events nest everything —
tripOrders[](one per activity) each containbookings[](one per guest), with customer, ticket type, custom fields, add-ons, and status. -
customer_portal_url— a login-free link to the guest's portal — is included on every trip-related event.trip_created,trip_reserved,trip_updated,trip_cancelled, andpayment_created(nested undertrip) include it at all three levels: the root trip, each trip order, and each booking.booking_updatedandaddon_updatedinclude it once, at the booking level only. It's not included onwaiver_signed,payout,customer_create, orcustomer_update.
customer_create
Webhook payload example
{
"event": "customer_create",
"id": 5567611,
"code": "CUST-1A2B",
"first_name": "Jordan",
"last_name": "Avery",
"full_name": "Jordan Avery",
"email": "jordan@example.com",
"phone": "+15550100",
"phone_format_intl": "+1 555-0100",
"phone_format_164": "+15550100",
"phone_country_code": "US",
"mappable_position": "Denver Colorado United States",
"location_description": "Denver, CO, USA",
"country": {},
"is_vip": false,
"is_opt_in": true,
"timezone": "America/Denver",
"created_at": "2026-02-26T00:20:59+00:00"
}
trip_created
trip_reserved, trip_updated, and trip_cancelled share this same trip shape — the event, trip_status, and amounts differ.
Webhook payload example
{
"event": "trip_created",
"id": 2088161,
"confirmation_code": "BOOK-1234",
"display_name": "Jordan Avery",
"created_at": "2026-02-26T00:05:34+00:00",
"reserved_at": "2026-02-26T00:05:57+00:00",
"sale_date": "2026-02-28T00:00:00+00:00",
"trip_status": { "slug": "draft", "name": "Draft" },
"trip_method": { "name": "Walkup", "color": "#3498eb", "slug": "walkup" },
"customer_portal_url": "https://portal.tripworks.com/t/abc123",
"customer": {
"first_name": "Jordan",
"last_name": "Avery",
"full_name": "Jordan Avery",
"email": "jordan@example.com",
"phone": "+15550100",
"phone_format_intl": "+1 555-0100"
},
"tripOrders": [
{
"created_at": "2026-02-26T00:05:34+00:00",
"experience_total": 20000,
"addons_total": 3000,
"products_total": 0,
"pickup_total": 0,
"promo_code_total": 0,
"voucher_code_discount": 0,
"total_sales": 23000,
"customer_portal_url": "https://portal.tripworks.com/t/abc123?order=1",
"experience": {
"id": 105,
"name": "Hiking",
"internal_name": "Hiking",
"duration": 480,
"color": "#4e5dc1"
},
"experience_timeslot": {
"label": "11:00 AM",
"start_time": "2026-02-28T11:00:00+00:00",
"end_time": "2026-02-28T19:00:00+00:00"
},
"status": { "id": 1, "slug": "booked", "name": "Booked" },
"pax_count": 1,
"addons_cnt": 2,
"bookings": [
{
"id": 5201661,
"created_at": "2026-02-26T00:05:34+00:00",
"status": { "id": 15, "name": "Not Checked In" },
"is_complimentary": false,
"customer_portal_url": "https://portal.tripworks.com/t/abc123?booking=1",
"customer": {
"first_name": "Jordan",
"last_name": "Avery",
"full_name": "Jordan Avery",
"email": "jordan@example.com",
"phone": "+15550100"
},
"experience_customer_type": {
"id": 195,
"name": "Adult",
"description": "price per person"
},
"custom_field_values": [],
"booking_resources": [],
"addons": [
{ "id": 371942, "name": "Ultimate Hike", "price": 25000, "experience_addon": { "id": 31, "title": "Ultimate Hike" } },
{ "id": 371943, "name": "Lunch", "price": 1000, "experience_addon": { "id": 6515, "title": "Lunch" } }
]
}
]
}
],
"custom_field_values": [
{ "custom_field": { "internal_name": "Team Color" } },
{ "custom_field": { "internal_name": "Shirts Needed" } }
],
"payments": [],
"subtotal": 23000,
"total": 24200,
"paid": 0,
"due": 24200,
"total_sales": 24200,
"products_cnt": 0,
"addons_cnt": 2,
"reseller_commission": 0,
"session_ip_address": "203.0.113.10",
"session_browser_name": "Chrome",
"session_browser_os": "Apple",
"session_location_label": "Denver, Colorado US",
"created_by_user_avatar": {
"id": 58,
"first_name": "Taylor",
"last_name": "Reed",
"full_name": "Taylor Reed",
"abbrev_name": "Taylor R."
}
}
payment_created
A payment (or refund) wraps the trip under a trip key.
Webhook payload example
{
"event": "payment_created",
"id": 1277113,
"created_at": "2026-02-26T00:20:05+00:00",
"amount": 5000,
"net_amount": 5000,
"tip": 0,
"currency": "USD",
"is_card_present": false,
"status": { "id": 6, "name": "Successful" },
"type": { "id": 1, "name": "Cash" },
"direction": { "id": 1, "name": "Payment" },
"owner": {
"id": 58,
"first_name": "Taylor",
"last_name": "Reed",
"full_name": "Taylor Reed"
},
"trip": {
"id": 2086899,
"confirmation_code": "BOOK-5678",
"trip_status": { "slug": "reserved", "name": "Reserved" },
"customer_portal_url": "https://portal.tripworks.com/t/def456",
"subtotal": 96000,
"total": 106377,
"paid": 5000,
"due": 101377
}
}
waiver_signed
Webhook payload example
{
"event": "waiver_signed",
"id": 1081786,
"created_at": "2026-02-26T00:27:50+00:00",
"full_name": "Jordan Avery",
"first_name": "Jordan",
"last_name": "Avery",
"email": "jordan@example.com",
"phone": "+15550100",
"dob": "2006-04-14T00:00:00+00:00",
"is_waiver_adult": true,
"experience_name": "Swimming",
"waiver_type": { "id": 2, "name": "Liability Waiver" },
"signer_category": "lead",
"booking_id": 5197289,
"signed_pdf": "https://cdn-images.tripworks.com/tenants/profiles/example/waivers/example.pdf"
}
Example: what people build with this
-
Sync every new booking into a CRM — subscribe to
trip_createdandtrip_reserved, and push each guest into your CRM the moment they book, instead of exporting a list by hand at the end of the day. -
Post a Slack alert on a big payment — subscribe to
payment_created, and have your receiver checkamount(remember, it's in integer cents) before deciding whether to notify the team. - Feed a data warehouse for reporting — subscribe to all 11 events and land the raw JSON in a table per event type, so you can build reports TripWorks doesn't offer natively.
-
Trigger a welcome email from your own system — subscribe to
customer_createand kick off a sequence in your own email tool the moment a new guest's profile is created. -
Cancel a linked reservation in another system — subscribe to
trip_cancelledso a booking cancelled in TripWorks also cancels whatever downstream record (a shuttle reservation, an equipment hold) depends on it.
How delivery works
- TripWorks sends one
POSTper event to each endpoint you listed, a few seconds after the event. - Your endpoint must return HTTP 200. Any other final status, including 201 or 202, is treated as a failure. A redirect is followed automatically and judged by whatever status the redirect target returns, so a 301/302 to a URL that itself returns 200 counts as a success.
- The request times out after 15 seconds — respond quickly and do heavy work asynchronously on your side.
- There are no automatic retries and no auto-disable. If your endpoint is down, that event is simply missed — TripWorks won't resend it, and the endpoint keeps trying on future events. Build your receiver to be reliable (return 200 fast, queue the work).
Security
TripWorks does not sign webhook requests — there's no shared secret or signature header, so a receiver can't cryptographically verify that a request truly came from TripWorks. Protect your endpoint accordingly:
- Treat the URL as a secret. Use a long, unguessable path (the setup field even suggests a random-looking URL), and don't share it.
- Require HTTPS so the payload is encrypted in transit.
- Add your own check if you need one — for example, include a secret token in the URL's path or query string and reject requests that don't carry it.
Test and troubleshoot
There's no "send test event" button — webhooks fire on real actions — so you test by triggering the real thing:
- Set a webhook.site URL (or your own inspector) as the endpoint for an event.
- Perform that action in your live account — e.g. create a trip for
trip_created. - Watch the
POSTarrive and inspect the JSON.
If nothing arrives: confirm Enable Webhooks is on, the URL is correct and returns 200, and that you actually performed an action that fires the event you subscribed to.
Frequently asked questions
Does TripWorks retry a failed delivery?
No. Each event is a single POST to each endpoint. If your server is down or returns a non-200 status, that event is missed and isn't resent. Make your receiver return 200 quickly and do the real work in the background.
How do I verify a request really came from TripWorks?
Requests aren't signed, so there's no signature to verify. Keep the endpoint URL secret, require HTTPS, and if you need stronger assurance, include a secret token in the URL and check for it.
Why isn't my webhook firing when I test it?
There's no test mode — webhooks only fire on real actions. Trigger the actual event (for example, create a booking for trip_created) with an inspector URL like webhook.site set as the endpoint, and check that Enable Webhooks is on and the URL returns 200.
Should I use webhooks or Zapier?
They fire on the same events. Use Zapier for no-code connections to thousands of apps; use webhooks to POST raw JSON to your own system. Pick one path per event so downstream processing doesn't happen twice.
Why is a money amount ten or a hundred times too big?
Amounts are in integer cents. Divide by 100 — 24200 is $242.00.
Related
Built for attractions, tours & activities
Wire TripWorks into everything else
See how attractions, tours, and activities push bookings, payments, and waivers into their own systems in real time on TripWorks. Book a demo and make the switch.