Building a Custom Booking Platform on WordPress with Stripe Webhooks
WordPress gets dismissed as "just a blogging tool," but with custom PHP it's a legitimate application platform — one your client's team already knows how to operate. I built a complete booking and financial management system for a group travel company on it, and the result handles real money, real bookings, and complex per-user pricing every day. Here's how the interesting parts work.
The problem: bookings aren't checkouts
A travel booking isn't an e-commerce checkout. People pay a deposit first, then pay the balance in chunks, sometimes on custom terms negotiated per person. They join trips, drop out, and want to see exactly what they owe. Off-the-shelf booking plugins model none of this, which is why the build needed a custom payment engine rather than a plugin install.
A multi-context Stripe payment engine
The core design decision: payments can start from anywhere in the user journey. The same engine serves:
- an on-page deposit form on each trip page,
- a "Manage My Trips" form where users pay down an existing balance,
- admin-initiated payment requests with custom amounts.
Each context creates a Stripe payment with metadata identifying the user, the trip, and the payment type. The critical rule I follow with every payments build: the front-end never decides whether a payment succeeded. A custom Stripe webhook endpoint is the single source of truth. It validates the event signature, updates guest counts, moves the user between lists, and appends the transaction to their personal ledger. If the webhook didn't say it happened, it didn't happen.
This matters because users close tabs mid-payment, mobile connections drop after the card is charged, and retry loops double-submit forms. Webhook-driven state survives all of that.
Social proof as a conversion engine
Each trip has two lists: "Wanna Go" (wishlist) and "Going" (deposit paid). When the webhook confirms a deposit, the user automatically migrates from one list to the other. The attendee lists render as rich profile cards — and for logged-out visitors they're blurred behind a "Login to View" call-to-action.
That one feature did double duty: it created urgency (you can see a trip filling up) and became the site's best lead-generation tool, because seeing who else is going is exactly the information a hesitant booker wants.
Per-user invoicing with an override engine
Group travel pricing is messy: early-bird rates, negotiated discounts, custom payment schedules. The invoicing system has two layers:
- Trip defaults — admins set standard line items and due dates once per trip.
- Per-user overrides — any line item, due date, or instruction can be overridden for a specific person.
When a user clicks "Download Invoice," the system resolves their effective pricing (override → default), generates a watermarked PDF, and either downloads or emails it. Admins stopped producing invoices by hand entirely.
The DNS war story
Mid-project, the client's email and site went down together — bookings stopped, confirmation emails bounced, and the business was effectively offline. The cause was a misconfigured DNS migration by a previous provider: conflicting MX records and a nameserver delegation pointing at a dead zone.
Untangling it meant auditing every record, re-delegating the zone, and rebuilding SPF/DKIM so booking confirmations stopped landing in spam. The lesson I now apply to every project: infrastructure is part of the product. A flawless booking engine is worthless if DNS routes payments confirmations into the void. I now set up and document DNS, email authentication, and hosting on every client build — even when "that's not the project."
When WordPress is the right call
This system could have been a custom Next.js + Postgres app. WordPress won because the client's team needed to manage trips, content, and pricing themselves — and they already lived in wp-admin daily. Advanced Custom Fields gave them structured trip configuration without touching code, and custom PHP handled everything the platform couldn't.
The right stack is the one the client can operate after you leave. If you're weighing a similar build — custom booking flows, Stripe integration, or rescuing a WordPress project that's outgrown its plugins — I take on projects like this; the chat assistant on this site can put us in touch.