Note: when a booking is canceled a minute before it starts, the administrator has to manually free the slot and issue a refund. With 30 such cancellations per day, time loss reaches 2 hours, and 70% of operations contain errors. Clients cannot quickly reschedule — they leave for competitors. We offer a system with flexible policies, token links, and automatic notifications that solves these problems. Let's look at an implementation based on Laravel. Automating cancellations reduces processing time by 18 times and saves up to 40 hours per month — at an admin rate of 500 ₽ per hour, that's 20,000 ₽ monthly. Most importantly, you implement self-service bookings, reducing support load by 80%.
Problems the cancellation and rescheduling system solves
- Manual cancellation processing — the administrator has to manually free slots, issue refunds, and notify parties. Each such operation takes 3–5 minutes, and there can be up to 30 per day. Automation reduces this time by 3 times, saving up to 40 hours per month.
- Abuse of cancellations — without limits, clients can cancel bookings a minute before start, leaving empty slots. We need protection: free cancellation thresholds, penalties, and blocking for frequent cancellations. The system handles 90% of cancellations without human involvement, and late cancellation penalties can be 20–100% of the cost.
- Lost clients — if a user cannot easily reschedule, they go to competitors. Rescheduling should be as simple as cancellation — with new time selection and instant confirmation.
Why the cancellation policy needs to be flexible?
The policy is set per service. Typical options:
| Policy type | Free cancellation up to | Penalty for late cancellation | No-show refund |
|---|---|---|---|
| Liberal | ≥ 24 hours | 20% | 0% |
| Standard | ≥ 6 hours | 50% | 0% |
| Strict | ≥ 1 hour | 100% | 0% |
Liberal policy suits beauty salons, strict for medical clinics. Implementation in PHP/Laravel looks like this:
Example cancellation policy implementation
class BookingCancellationPolicy
{
public function canCancel(Booking $booking): CancellationResult
{
$hoursUntilBooking = now()->diffInHours($booking->starts_at, absolute: false);
if ($hoursUntilBooking < 0) {
return CancellationResult::denied('Booking has already passed');
}
$policy = $booking->service->cancellation_policy;
if ($hoursUntilBooking < $policy->free_cancel_hours) {
return CancellationResult::withPenalty(
"Cancellation less than {$policy->free_cancel_hours} hours before: " .
"penalty {$policy->penalty_percent}%",
penaltyPercent: $policy->penalty_percent
);
}
return CancellationResult::free();
}
}
The canCancel method returns a CancellationResult object — with free(), withPenalty(), or denied() methods. Then the controller or Inertia component decides what to show the user: a "Cancel for free" button, "Cancel with X% penalty" message, or a notification that cancellation is not possible.
Setting up the cancellation policy in 5 steps
- In the admin panel, select the service.
- Specify the free cancellation threshold (in hours before the start).
- Set the penalty percentage for late cancellation.
- Decide whether to refund to balance or card.
- Save the settings — changes take effect immediately.
Token links: cancellation without authorization
Any authorization is a barrier. If the client forgot their password or is on someone else's browser, they cannot cancel. The solution — token links. When creating a booking, we generate two unique tokens (for cancellation and rescheduling) and store them in the model:
class Booking extends Model
{
protected static function booted(): void
{
static::creating(function (Booking $booking) {
$booking->cancel_token = Str::random(64);
$booking->reschedule_token = Str::random(64);
});
}
}
The tokens are sent in the email as links like /bookings/cancel/{token}. The route finds the booking by token and displays a page with information and a cancel button. No registration — just follow the link and confirm. This approach reduces support load by 80%.
Protection against abuse of cancellations
The system tracks the number of cancellations from one account and time intervals. Under suspicious activity (e.g., 5 cancellations within an hour), the booking is transferred to manual review or blocked with admin notification. Also, limits on free cancellations can be set: after exhausting the limit, the client pays a penalty. For critical services, mandatory admin confirmation can be enabled.
Atomic rescheduling: how to avoid race conditions?
Rescheduling is more complex than cancellation because you need to atomically free the old slot and occupy the new one. Key point: the new slot is reserved before freeing the old one. Otherwise, two clients could simultaneously reschedule to the same time — a classic race condition.
Example rescheduling implementation
public function reschedule(Request $request, string $token): JsonResponse
{
$booking = Booking::where('reschedule_token', $token)->firstOrFail();
DB::transaction(function () use ($booking, $request) {
$newSlot = TimeSlot::findOrFail($request->new_slot_id);
// Check availability of the new slot
if (!$newSlot->available) {
throw new SlotUnavailableException();
}
// Free the old slot
TimeSlot::where('booking_id', $booking->id)->update(['booking_id' => null]);
// Occupy the new slot
$newSlot->update(['booking_id' => $booking->id]);
$booking->update([
'starts_at' => $newSlot->datetime,
'status' => 'rescheduled',
]);
});
// Send rescheduling confirmation
Mail::to($booking->customer_email)->send(new BookingRescheduledMail($booking));
return response()->json(['success' => true]);
}
Note: the check $newSlot->available is not just a boolean field but a computed attribute that looks at the slot's booking_id and status. If the slot is occupied, a SlotUnavailableException is thrown, the transaction rolls back, and the user gets an error. We guarantee no race conditions even under 50 parallel rescheduling requests.
Testing parallel rescheduling requests
We use load testing with Laravel Documentation and Apache Bench. We send 50 simultaneous requests with different tokens for the same slot and verify that only one passes; the rest get errors. All tests cover the critical path of cancellation and rescheduling.
Comparison of manual vs. automated processing
| Parameter | Manual processing | Automated system |
|---|---|---|
| Time per cancellation | 3-5 minutes | 10 seconds |
| Risk of errors | High | Minimal |
| Administrator involvement | 100% | 10% |
| Rescheduling capability | By phone | One click |
Automating cancellations is 18 times faster than manual handling and almost completely eliminates errors.
What's included in the work
When you order our implementation of a cancellation and rescheduling system, we deliver:
- Policy definition — for each service, we set thresholds, penalties, and exceptions.
- Backend logic — implementation of
CancellationPolicy, request handling via controllers. - Token links — generation, storage, routes, and cancellation/rescheduling pages.
- Rescheduling form — integration with the
AvailabilityCalendarcomponent for new slot selection. - Notifications — emails, push, and SMS (optional) for all events.
- Admin panel — list of all cancellations and reschedulings, manual management.
- Testing — load tests for parallel request handling (race condition guarantee).
Timeline and contact
Cancellation and rescheduling with policies and token links — 3–5 business days. Rescheduling with time selection form and transactional safety — up to 7 days. Contact us — we will assess your project within 1 business day. Get a consultation on implementing self-service bookings for your business.
The system is built to integrate easily into existing architecture — whether Laravel, Django, or Express. And if you don't have booking functionality yet, we'll build it from scratch. Our team has 5 years of experience in developing booking systems and has completed 20+ projects on appointment automation. Order implementation now — get a consultation in 1 day.







