---
title: Cancel a booking via its signed link (no auth)
description: Cancels one booking, authorized ONLY by the HMAC token minted at booking time (action- and uid-bound, expiring at the booking start).
api_method: POST
api_path: "/v1/public/bookings/{uid}/cancel"
canonical_url: https://wiblo.app/docs/developers/api/public/cancel-public-booking
last_updated: 2026-07-28T18:08:25+02:00
md_url: https://wiblo.app/docs/developers/api/public/cancel-public-booking.md
---

# Cancel a booking via its signed link (no auth)

`POST /v1/public/bookings/{uid}/cancel`

Cancels one booking, authorized ONLY by the HMAC token minted at booking time (action- and uid-bound, expiring at the booking start). A tampered, cross-action, cross-booking, or expired token fails closed with 401 before any database work. The cancel itself is the house soft delete with `cancelled_by` recorded as `attendee`. Aggressively rate-limited per IP at 10/60s and wrapped by Vercel BotID.

## Path parameters

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `uid` | `string` | Yes |  |

## Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `token` | `string` | Yes | The `cancel_token` minted at booking time. Tampered, cross-action, cross-booking, and expired tokens all answer 401 `INVALID_TOKEN`. |
| `reason` | `string` | No | Free-text cancellation reason, recorded on the booking. |

## Request

**curl**

```bash
curl https://api.wiblo.app/v1/public/bookings/a1f8c3e6-2d7b-4c9a-8e5f-1b4d7a2c9e6f/cancel \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{
  "token": "v1.cancel.1786960800.3fQ8yWvKpLm2XcJ9RtBz6HdN4kGaU7oEs1MiZP5vYqA",
  "reason": "Schedule conflict on my side."
}'
```

**TypeScript**

```ts
import { createSdk, cancelPublicBooking } from "@workspace/sdk"

const sdk = createSdk({ baseUrl: "https://api.wiblo.app" })

const { data, error } = await cancelPublicBooking({
  client: sdk,
  path: { uid: "a1f8c3e6-2d7b-4c9a-8e5f-1b4d7a2c9e6f" },
  body: {
    "token": "v1.cancel.1786960800.3fQ8yWvKpLm2XcJ9RtBz6HdN4kGaU7oEs1MiZP5vYqA",
    "reason": "Schedule conflict on my side."
  },
})
```

## Example response

```json
{
  "booking": {
    "booking_id": "e7c4a9d2-8b1f-4e6a-9d3c-5a8f2b7e1c4d",
    "booking_uid": "a1f8c3e6-2d7b-4c9a-8e5f-1b4d7a2c9e6f",
    "status": "cancelled",
    "cancelled": true,
    "message": "Booking cancelled successfully"
  }
}
```

## Responses

**`200`** — The booking was cancelled. Returns `PublicCancelBookingResponse`.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `booking` | `object` | Yes | The cancelled booking's summary. |
| `booking.booking_id` | `string` | Yes | Unique id of the cancelled booking. |
| `booking.booking_uid` | `string` | Yes | Public uid of the cancelled booking — the value its link tokens are bound to. |
| `booking.status` | `"cancelled"` | Yes | Always `cancelled` on this surface. |
| `booking.cancelled` | `boolean` | Yes | `true` when the cancel was applied; replaying the link on an already-cancelled booking is rejected instead. |
| `booking.message` | `string` | Yes | Human-readable confirmation message. |

**`400`** — Body failed Zod validation (`VALIDATION_FAILED`). Returns `ApiErrorEnvelope`.

**`401`** — The signed link token is invalid or expired (`INVALID_TOKEN`). Returns `ApiErrorEnvelope`.

**`404`** — No booking carries that uid (`BOOKING_NOT_FOUND`). Returns `ApiErrorEnvelope`.

**`429`** — Rate limit exceeded. The body's `error.code` is `RATE_LIMITED` and `error.details.retry_after` is the same number of seconds as the `Retry-After` header. Returns `ApiErrorEnvelope`.
