Examples
Working recipes — syncing bookings, building a custom booking flow, verifying webhooks, exporting for accounting, and automating with n8n.
Copy-paste recipes for the integrations operators build most often.
1. Sync confirmed bookings to your warehouse
Pull yesterday's confirmed bookings on a schedule.
curl -G https://tripistic.com/api/workspaces/$WS/bookings \
-H "Authorization: Bearer $TRIPISTIC_API_KEY" \
--data-urlencode "status=CONFIRMED" \
--data-urlencode "from=2026-07-01" \
--data-urlencode "to=2026-07-02" \
--data-urlencode "limit=100"
async function syncDay(date: string) {
let cursor: string | undefined;
const rows = [];
do {
const query = new URLSearchParams({
status: "CONFIRMED",
from: date,
to: date,
limit: "100",
});
if (cursor) query.set("cursor", cursor);
const response = await fetch(
`https://tripistic.com/api/workspaces/${process.env.WS}/bookings?${query}`,
{ headers: { Authorization: `Bearer ${process.env.TRIPISTIC_API_KEY}` } },
);
if (!response.ok) throw new Error(`Sync failed: ${response.status}`);
const page = await response.json();
rows.push(...page.data);
cursor = page.pagination.hasMore ? page.pagination.nextCursor : undefined;
} while (cursor);
return rows;
}
Run it with a one-day overlap so a late webhook or timezone edge never drops a booking.
2. Build a custom booking flow
Use the public endpoints so no secret ever reaches the browser.
// 1. List bookable tours
const tours = await fetch(
`https://tripistic.com/api/public/${slug}/tours`,
).then((r) => r.json());
// 2. Fetch availability for the chosen tour and month
const availability = await fetch(
`https://tripistic.com/api/public/${slug}/tours/${tourSlug}/availability?from=2026-08-01&to=2026-08-31`,
).then((r) => r.json());
// 3. Create the booking
const booking = await fetch(`https://tripistic.com/api/public/${slug}/bookings`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
availabilityId: selected.id,
customer: { name, email, phone },
participants,
addOns,
}),
}).then((r) => r.json());
// 4. Send the traveller to checkout
window.location.href = booking.data.checkoutUrl;
Handle 409 in step 3 — the departure sold out while the traveller was choosing. Re-fetch availability and ask them to pick again.
3. Verify a webhook (Next.js route handler)
import { createHmac, timingSafeEqual } from "node:crypto";
export async function POST(request: Request) {
// Read the RAW body — parsing first breaks the signature.
const raw = await request.text();
const header = request.headers.get("tripistic-signature") ?? "";
if (!verify(raw, header, process.env.TRIPISTIC_WEBHOOK_SECRET!)) {
return new Response("Invalid signature", { status: 401 });
}
const event = JSON.parse(raw);
// Acknowledge immediately; do the work asynchronously.
await queue.enqueue(event);
return new Response("ok", { status: 200 });
}
function verify(raw: string, header: string, secret: string): boolean {
const parts = Object.fromEntries(
header.split(",").map((p) => p.split("=") as [string, string]),
);
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
const expected = createHmac("sha256", secret).update(`${parts.t}.${raw}`).digest("hex");
const a = Buffer.from(expected, "hex");
const b = Buffer.from(parts.v1 ?? "", "hex");
return a.length === b.length && timingSafeEqual(a, b);
}
4. Push new bookings to Slack
// Handler for the booking.confirmed event
async function onBookingConfirmed(event: WebhookEvent) {
const booking = event.data;
const amount = (booking.totalAmount / 100).toFixed(2);
await fetch(process.env.SLACK_WEBHOOK_URL!, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
text: `New booking · ${booking.participantCount} pax · ${amount} ${booking.currency}`,
}),
});
}
5. Export for accounting
const rows = [];
for await (const booking of client.paginate("/bookings")) {
if (booking.paymentStatus !== "PAID") continue;
rows.push({
reference: booking.id,
date: booking.confirmedAt,
customer: booking.customer.name,
gross: booking.totalAmount / 100,
currency: booking.currency,
paymentIntent: booking.paymentIntentId, // reconcile against Stripe payouts
});
}
Reconcile paymentIntent against your Stripe payout report rather than matching on amount — amounts collide, references do not.
6. Automate with n8n or Zapier
Trigger: a Tripistic webhook on booking.confirmed.
Steps:
- Verify the signature in a code node.
- Look up the customer in your accounting tool by email.
- Create or update the contact.
- Create the invoice line from the booking.
- Write the invoice reference back with
PATCH /bookings/{id}in the booking notes.
Filter early — send only the events you actually need to the automation platform, or you will pay for noise.
7. Nightly staff assignment audit
const tomorrow = new Date(Date.now() + 86_400_000).toISOString().slice(0, 10);
const departures = await fetch(
`https://tripistic.com/api/workspaces/${WS}/availability?from=${tomorrow}&to=${tomorrow}`,
{ headers: { Authorization: `Bearer ${KEY}` } },
).then((r) => r.json());
const unstaffed = departures.data.filter(
(d) => d.bookedSeats > 0 && (!d.guideId || (d.requiresVehicle && !d.vehicleId)),
);
if (unstaffed.length > 0) await alertOperations(unstaffed);
Catching an unstaffed departure at 18:00 the night before is considerably cheaper than at 08:00 on the day.
Related
- REST API · Webhooks · SDKs · Rate Limits