# Pay with Wellpass integration guide

This guide explains how to connect your booking website to **Pay with Wellpass**, so Wellpass members can pay for classes and sessions on your platform using booking codes from their Wellpass app. It covers validating a code, confirming a booking, cancelling a booking, handling errors, and testing your integration in the sandbox.

Geographic availability
This integration is currently only available in France and Belgium.

Who is this guide for?
This guide is for **booking platform partners** (websites and apps where users book classes at gyms and studios). If you are a gym operator integrating Wellpass memberships into your member management system, see the [Wellpass integration guide](/mms-api-v2/tutorials/wellpass-integration) instead.

## What is Pay with Wellpass?

Wellpass is a corporate fitness program that lets employees access a network of gyms and studios as part of their employer's health benefits. With **Pay with Wellpass**, a member pays for a single class on your booking website using a **booking code**: a single-use code the member generates in their Wellpass app for a specific gym, paid with their Wellpass credits.

### Key concepts

- **Booking code** — a single-use code generated by the member in the Wellpass app. It is tied to one gym and expires if not used.
- **`clubId`** — the identifier of the gym location the class takes place at. You receive the `clubId` values for your connected gyms during onboarding.
- **Dry run** — the same endpoint both validates and redeems a code. With `dryRun: true` the code is only [validated](#validate-a-code); with `dryRun: false` the code is [redeemed and the booking is created](#confirm-a-booking).
- **Payout** — the amount confirmed in the validation response. It reflects the accepted class price.


## The user journey

How the user interacts with your platform is up to you. This is the part of the journey that involves the Pay with Wellpass API:

```
User generates a booking code in the Wellpass app (for a specific gym)
         ↓
User books a class on your platform, paying with the code
and the email address they use with Wellpass
         ↓
Your system validates the code (validate, dryRun: true)
         ↓
Your system confirms the booking (validate, dryRun: false)
         ↓
The code is redeemed and the member's credits are charged
```

## Authentication

All requests go through the **EGYM partner gateway**, the single entry point for partner integrations. The `{baseUrl}` used throughout this guide is the gateway's address: the gateway authenticates your request first and then forwards it to the Pay with Wellpass service, so you never call that service directly.

Requests are authenticated with an API key sent in the `Authorization` request header as a `Bearer` token. API keys are issued per partner and can be retrieved through the EGYM Partner Integration Portal, which you get access to during onboarding (see the [integration guide](/mms-api-v2/guide)). Keep your keys secure and never expose them in client-side code.

Only HTTPS is supported. Requests made over plain HTTP will fail.

```bash
curl -X POST "{baseUrl}/rest/paymentmethod/validate" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ ... }'
```

Base URL
`{baseUrl}` is a placeholder. You will receive the base URLs during onboarding.

## Validate a code

The user needs to generate a code in the Wellpass app. Before creating a reservation, verify that the user's booking code is valid for the selected class. Call the validate endpoint with `dryRun: true`. This validates the code **without redeeming it** (without creating a reservation/booking).

`POST {baseUrl}/rest/paymentmethod/validate`

### Request fields

| Field | Type | Required | Description |
|  --- | --- | --- | --- |
| `code` | string | yes | The booking code the user generated in the Wellpass app. |
| `price` | number | yes | The price of the class-session. Must be greater than `0`, e.g. `12.50`. |
| `startDate` | string (ISO 8601) | yes | Start date and time of the class, e.g. `2030-07-15T10:00:00Z`. Must be in the future (a tolerance of 5 minutes in the past is allowed). |
| `duration` | integer | yes | Duration of the class in minutes. Must be positive, e.g. `60`. |
| `clubId` | string | yes | Identifier of the gym location where the class takes place. |
| `email` | string | yes | The email address the user has registered with Wellpass. |
| `dryRun` | boolean | yes | `true` to only validate the code. `false` to redeem it and create the booking. |


### Example: validate a code

```bash
curl -X POST "{baseUrl}/rest/paymentmethod/validate" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "code": "ABC123XYZ",
    "price": 12.50,
    "startDate": "2030-07-15T10:00:00Z",
    "duration": 60,
    "clubId": "your-club-id",
    "email": "jane.doe@company.com",
    "dryRun": true
  }'
```

### Successful response — `200 OK`

```json
{
  "data": {
    "payout": 12.5,
    "customer": {
      "firstname": "Jane",
      "lastname": "Doe"
    },
    "gym": {
      "name": "Example Gym Berlin"
    }
  }
}
```

You can use the returned customer name and gym name, for example, to let the user confirm the details before you complete the booking.

What is validated?
A successful dry run confirms that: the code exists, has not been redeemed or expired, and belongs to the given gym; the email matches the owner of the code; the price does not exceed the maximum allowed for the gym; and the user has no other bookings overlapping with the class time.

## Confirm a booking

To redeem the code and create the reservation, send the **same request** as for [validating a code](#validate-a-code) with `dryRun: false`.

```bash
curl -X POST "{baseUrl}/rest/paymentmethod/validate" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "code": "ABC123XYZ",
    "price": 12.50,
    "startDate": "2030-07-15T10:00:00Z",
    "duration": 60,
    "clubId": "your-club-id",
    "email": "jane.doe@company.com",
    "dryRun": false
  }'
```

On success the response has the same shape as the dry run (`200 OK` with `payout`, `customer` and `gym`). At this point:

- The booking code is **redeemed** and cannot be used again.
- The booking is created and the member's Wellpass credits are charged (if applicable).


Always dry run first
All validations are executed again when you confirm with `dryRun: false`, so a booking that passed the dry run can still fail on confirmation (for example, if the code was redeemed elsewhere in the meantime). Handle errors on both calls.

## Cancel a booking

If a class is cancelled (by the user or by the gym), let Wellpass know so the member gets their credits back.

There are two scenarios that end in the same API call. How the user cancels on your platform is up to you:

```
User cancels his booking          Gym or studio cancels the class
        ↓                                   ↓
        └────────────┬──────────────────────┘
                     ↓
Your system cancels the booking (cancel)
  • on time → the member gets their credits back
  • after your cancellation deadline → send isLateCancel: true
```

When the gym or studio calls off the class, cancel every affected Pay with Wellpass booking. The `reason` field is the place to say why (e.g. "Class cancelled by the studio"). A class cancelled by the gym should not be flagged as a late cancel.

`POST {baseUrl}/rest/paymentmethod/cancel`

### Request fields

| Field | Type | Required | Description |
|  --- | --- | --- | --- |
| `code` | string | yes | The booking code used for the booking. |
| `clubId` | string | yes | Identifier of the gym location of the booking. |
| `isLateCancel` | boolean | no | Set to `true` if the cancellation happened after your cancellation deadline. |
| `reason` | string | no | Free-text reason for the cancellation. |


### Example: cancel a booking

```bash
curl -X POST "{baseUrl}/rest/paymentmethod/cancel" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "code": "ABC123XYZ",
    "clubId": "your-club-id",
    "isLateCancel": false,
    "reason": "Class cancelled by the studio"
  }'
```

### Successful response — `204 No Content`

The response has no body. Cancellation semantics:

- An **on-time cancellation** cancels the booking and the member's credits are returned.
- A cancellation flagged with `isLateCancel: true`, or one that happens after the booking's cancellation window, is recorded as a **late cancellation**.
- The endpoint is **idempotent**: cancelling an already-cancelled booking returns `204` without any further effect.
- A booking whose class has already ended cannot be cancelled (see [`BookingInThePast`](#cancellation-errors)).


## Error handling

There are two error formats.

**Payload validation errors** (`400 Bad Request`) are returned when required fields are missing or malformed:

```json
{
  "statusCode": 400,
  "error": "Bad Request",
  "message": [
    "code should not be empty",
    "startDate must be in the future or present"
  ]
}
```

**Business errors** are returned when the request is well-formed but the code, booking, or gym state prevents the operation:

```json
{
  "type": "NoPassFoundWithProvidedCode",
  "description": "No pass found with provided code",
  "possibleExplanation": "The code provided is wrong, most likely a typo or it has the wrong status or the code has expired",
  "data": {
    "code": "ABC123XYZ"
  }
}
```

Use the `type` field to react programmatically and show the user a helpful message.

### Validation errors

| `type` | HTTP status | Meaning and what to do |
|  --- | --- | --- |
| `NoPassFoundWithProvidedCode` | 400 | The code is wrong, already redeemed, or **expired**. Ask the user to re-check the code or generate a new one in the Wellpass app. |
| `EmailDoesNotMatchOwner` | 400 | The email does not match the owner of the code. Ask the user to use the same email they registered with Wellpass. |
| `PassDoesNotBelongToGym` | 400 | The code was generated for a different gym. Ask the user to generate a code for the correct gym. |
| `UserCannotBookClass` | 400 | The user is not eligible to book this class (see [the possible causes below](#what-can-cause-usercannotbookclass-)). |
| `SessionPriceIsTooHigh` | 400 | The submitted price exceeds the maximum allowed for this gym. The user likely selected the wrong offer. |
| `NoGymFoundWithProvidedClubId` | 400 | The `clubId` is not known. Verify your gym information and contact Wellpass if it persists. |
| `PayWithPassCodeDisabledOnGym` | 400 | Pay with Wellpass is not enabled for this gym. Contact Wellpass to enable it. |
| `NoPricingDataAssociatedOnGymError` | 400 | The gym has no pricing data configured. Contact Wellpass. |


### What can cause `UserCannotBookClass`?

`UserCannotBookClass` is a generic "user is not eligible" error covering several situations:

- **Overlapping booking** — the user already has a booking that overlaps with this class time.
- **No active membership** — the user's Wellpass membership is not active right now, or will not be active at the class start date (for example, the membership ends before the class takes place).
- **Wrong membership type** — the user's membership is not eligible for booking at this gym.
- **Insufficient credits** — on confirmation (`dryRun: false`), charging the member's credit wallet failed, for example because they don't have enough credits.


The error response does not distinguish between these causes, so show the user a generic message like *"Your Wellpass membership doesn't allow this booking. Please check your membership and existing bookings in the Wellpass app."*

### Cancellation errors

| `type` | HTTP status | Meaning and what to do |
|  --- | --- | --- |
| `BookingNotFound` | 404 | No booking exists for the given code/gym combination. Verify the `code` and `clubId`. |
| `BookingInThePast` | 400 | The class has already ended and can no longer be cancelled. |
| `NotAPayWithWellpassBooking` | 400 | The booking was not created through Pay with Wellpass. |
| `NoGymFoundWithProvidedClubId` | 400 | The `clubId` is not known. Verify your gym information. |
| `PayWithWellpassNotEnabledOnGym` | 400 | Pay with Wellpass is not (or no longer) enabled for this gym. Contact Wellpass. |


### Example: expired or invalid code

A user enters a code they generated last month. The dry run fails with `400`:

```json
{
  "type": "NoPassFoundWithProvidedCode",
  "description": "No pass found with provided code",
  "possibleExplanation": "The code provided is wrong, most likely a typo or it has the wrong status or the code has expired",
  "data": { "code": "OLD123CODE" }
}
```

Show the user a message like *"This code is invalid or has expired. Please generate a new code in your Wellpass app."*

### Example: overlapping booking

A user who already booked a 10:00–11:00 class tries to book another class from 10:30. The request fails with `400`:

```json
{
  "type": "UserCannotBookClass",
  "description": "User cannot book this class",
  "data": { "code": "ABC123XYZ" }
}
```

Note that the response is the same `UserCannotBookClass` error the user would get for a membership problem. Your system cannot tell the causes apart, so prefer a message that covers all of them.

## Testing your integration

You can safely test the whole flow in the sandbox of the EGYM Partner Integration Portal (PIP), using **generated test codes** instead of real Wellpass members.

Access to EGYM Partner Integration Portal
Contact your Wellpass contact for portal access.

In the portal, open the **Wellpass** integration and start the **Code Validation** use case. It walks you through three chained test cases:

1. **Validate a code** — after you select one of your sandbox locations, the portal **generates a test booking code** for it and shows you the `code`, the `clubId` (sandbox club IDs have the format `pip-<locationId>`), and a `startDate`. Call the [validate endpoint](#validate-a-code) with this data and `dryRun: true`. Expected result: `200 OK`.
2. **Confirm the booking** — call the [validate endpoint](#confirm-a-booking) again with the same code and `dryRun: false`, using a `startDate` in the future. Expected result: `200 OK`.
3. **Cancel the booking** — call the [cancel endpoint](#cancel-a-booking) with the same `code` and `clubId`. Expected result: `204 No Content`.


The portal inspects each request your system sends and verifies the fields before marking the test case as passed. All three test cases are mandatory for completing the use case.

Test codes
Test codes are only issued as part of a sandbox test run in the Partner Integration Portal.