How We Used Hapio for a Small Massage Practice

I recently built a site for a solo massage therapist running her own practice. It’s the kind of project where the requirements are easy to state but easy to get wrong if you try to solve everything yourself: a presentable landing page, online booking that actually works, and a way for the owner to manage her schedule and treatments without calling me every time something changes.

This is how we chose to use Hapio, and why it worked well.

The Starting Point

My client already had a Facebook page and took bookings via Messenger. That works – until it doesn’t. Double bookings, forgotten appointments, manually answering the same questions about prices and availability. She wanted her own site where customers could book directly, and where she could close days, adjust opening hours, and add new treatments on her own.

The important thing: she’s a massage therapist, not a sysadmin. Everything we build has to make sense to someone who doesn’t think in terms of databases and APIs.

Why Not Build the Booking System Ourselves?

could have built a custom booking system. Laravel, a bookings table, some logic for available slots, conflict handling, buffer times between treatments, handling closed days, summer holidays…

But that’s exactly the kind of code that looks simple on a whiteboard and turns into six months of edge cases in practice. A single resource (one therapist), recurring opening hours with exceptions, buffers before and after each treatment, different treatment lengths – it’s not rocket science, but it’s a lot of code that doesn’t add unique value for this particular project.

Hapio handles that. We got a ready-made API for availability, bookings, services, and scheduling. I could focus on what the project was actually about: a nice site and a booking flow that feels like theirs, not like a generic booking system.

The Architecture We Landed On

We built a thin Laravel app as a wrapper around Hapio. Think “frontend + integration layer”, not “full booking system”.

Hapio is responsible for:

  • Bookings (create, fetch, cancel)
  • Services/treatments (name, duration, price, buffer times)
  • Recurring opening hours (Mon–Sun)
  • Closed individual days (holidays, sick days, whatever)
  • Calculating available slots based on all of the above

Laravel is responsible for:

  • The landing page and booking UI (Livewire)
  • Admin panel for schedule, services, and booking overview
  • Email (confirmation, reminder, cancellation – to both customer and owner)
  • Secure cancellation links (HMAC-signed, so customers can’t guess their way into someone else’s booking)
  • Caching available slots (Redis)
  • Webhook handling when Hapio data changes

The Laravel database basically only holds admin users, sessions, and queue jobs. No booking data is stored locally. Hapio is the source of truth.

That feels right for a business this size. A small local database, one Redis instance, nothing to maintain that isn’t needed.

The Booking Flow

The customer goes through four steps: choose treatment → choose time → fill in details → done.

Treatments are fetched from Hapio on page load. Times are fetched per week – we cache them aggressively in Redis because that’s the most heavily trafficked data. A week view where the customer can browse up to twelve weeks ahead.

When the booking is confirmed:

  1. The booking is created in Hapio
  2. Customer details (name, phone, email, optional message) are saved as metadata on the booking
  3. A cancellation link is generated (HMAC-signed with a secret key we control)
  4. Email is sent to the customer and the owner

Cancellation happens via the signed link – no login required. We have a 24-hour rule: you can’t book or cancel with less notice than that. The rule is applied in our code at read time, not in the cache, so it works consistently even if the cache hasn’t been invalidated yet.

The Admin Panel

The owner logs in and has four tabs:

  • Bookings – upcoming and past, with the ability to cancel
  • Schedule – click a day in the calendar to close or open it
  • Opening hours – the recurring weekly schedule, day by day
  • Services – add, edit, deactivate treatments

Everything goes through the Hapio API. The admin panel is essentially a nice frontend on top of their data model. When she changes opening hours or closes a day, Hapio sends a webhook, we invalidate the cache, and rebuild the time slots in the background.

It took a bit of work to get the webhook flow robust – signature verification, idempotency, fallback to a full cache flush if something goes wrong – but once it was in place, it was worth it. Available slots update quickly without us having to poll the API.

What Worked Well

Separation of concerns. We don’t have to maintain booking logic. Hapio handles conflicts, buffer times, and scheduling. We handle UX and what’s specific to the site.

Webhooks + cache. Available slots are cached for 24 hours. When something changes in Hapio (new booking, schedule change, new service), an event comes in, we invalidate the relevant cache and warm it back up. Customers see updated times without us having to fetch everything from the API on every page view.

Metadata on bookings. Customer details are stored as metadata on the Hapio booking. We don’t need a separate customer table. For a one-person business, that’s enough.

Cancellation links. HMAC-signed links in confirmation and reminder emails. The customer doesn’t need to log in or remember a booking number. We verify the token server-side before cancellation is allowed.

Admin without technical overhead. The owner can manage her schedule and services herself. I don’t need to be involved when she wants to close a day or add a new treatment.

Things We Had to Think About

SDK and error handling. We use Hapio’s PHP SDK as a path dependency. The SDK sometimes throws PHP warnings that drown out real error messages – we wrapped the calls and suppressed warnings so API errors actually come through.

The 24-hour rule. The business rule “at least 24 hours before booking/cancellation” is applied in our code at read time, not in Hapio. That means the cache can contain slots that aren’t shown to the customer – we filter them out when returning data. Simple and predictable.

One resource, one location. The configuration points to a single location and resource (the therapist). If the business grew to multiple therapists or locations, we’d need to rethink things, but for the current setup it’s perfect.

Summary

For a small massage practice with an owner who needs to manage everything herself, Hapio was the right choice. We didn’t build a booking system – we built a site and an integration layer that makes Hapio accessible in a way that fits the business.

Laravel + Livewire for UI and email. Hapio for all booking logic. Redis for cache. Webhooks to keep the cache in sync.

Thanks to Hapio – and AI helping along the way – it took roughly a weekend to build the whole site from scratch into something she could actually start using for her business.

It’s not the most complex architecture I’ve built, but it’s one of the ones that feels most proportionate to the project. And that’s what you want.

Draft. References to specific site, location, or person intentionally omitted.

Author

Mattias