Skip to content

Migrating from Back Office webhooks

Move each Back Office webhook to a Public API webhook, then turn the Back Office webhook off. This page is for developers whose receiver gets Back Office webhooks today. It maps every Back Office event to its Public API event, lists the field changes, and walks through the switch.

To build a new integration, start with Set up a webhook endpoint. If your company already receives Public API webhooks with historical names such as offer-update, read Existing V1 registrations instead.

Which webhooks are Back Office webhooks

Back Office webhooks are configured in the Back Office settings: Settings > Webhook in the previous Back Office, and the Legacy webhooks table of Settings > Webhooks & APIs in the new one. They are being retired. Public API webhooks are a separate system: the Public API registration endpoints never return a Back Office webhook.

What changes

The table compares the two systems. Each row shows what your receiver handles today and what it handles after the move.

Back Office webhook Public API webhook
Created in Back Office settings POST /api/v2/management/webhook-registrations/, or the Webhooks tab of Settings > Webhooks & APIs
Event names booking-update, member-create booking.updated, client.created
data The record under a key: data.booking, data.member The V2 resource itself: data.id, data.client_id
Field names Back Office names (member, offer, establishment) V2 REST names (client_id, class_id, venue_id)
Signature None Bsport-Signature header, HMAC-SHA256
Retries None: one attempt 4 retries with back-off for timeouts, network errors, 408, 425, 429 and 5xx

Both keep the same envelope: event_type, date (Unix timestamp) and data.

A similar event name does not mean the same payload

booking-update and booking.updated report the same change with different payloads. Map every field your receiver reads with the tables below, and use the event catalogue for the complete schema. Some IDs also change meaning: a Back Office teacher id and a Public API teacher id are different numbers for the same person.

Event mapping

The table lists every Back Office event. Find the event your webhook sends in the first column, register the event in the second column, and handle the changes in the third. In every Public API payload, data is the resource itself instead of a key such as data.booking.

Back Office event Public API event Main changes in data
booking-create, booking-update booking.created, booking.updated member -> client_id, offer -> class_id, offer_date_start -> class_start_at, consumer_payment_pack -> client_class_pass_id, establishment -> venue_id, date_canceled -> canceled_at. coach held a coach ID; teacher_id holds a teacher ID from /teachers/.
booking-delete booking.deleted data holds only the booking id.
offer-create, offer-update class.created, class.updated date_start -> starts_at, duration_minute -> duration_in_minutes, effectif -> max_capacity, meta_activity -> service_id, establishment -> venue_id. coach held a coach ID; teacher_id holds the main teacher's ID from /teachers/.
member-create, member-update client.created, client.updated name -> fullname, birthday -> birth_date, tags -> tag_ids, archived -> is_archived, credit_account_balance -> client_account_balance, default_billing_establishment -> default_billing_venue_id. The nested consumer object has no equivalent: read the client fields at the top level.
tag_member-applied, tag_member-removed client.tag_added, client.tag_removed data.member_tag.member -> data.client_id, data.member_tag.tag -> data.tag_id. The relation's own id is not sent.
associated_coach-create, associated_coach-update teacher.created, teacher.updated The teacher id is the Back Office associated_coach_id, not the Back Office id. phone -> phone_number, disabled -> is_disabled.
associated_coach-delete teacher.updated or teacher.deleted The Back Office sent this event when a teacher was archived. Archiving now sends teacher.updated with is_disabled set to true. teacher.deleted, with only the id, means the teacher record was deleted.
invoice-create, invoice-update invoice.created, invoice.updated New payload: build your parser from the event catalogue.
invoice-finalize, invoice-pay, invoice-dispute, invoice-revert invoice.updated, plus invoice.created for a reverse invoice No dedicated event: see Detect invoice lifecycle steps.
billing_plan-create, billing_plan-update None See Billing-plan webhooks.

The Public API also sends events that Back Office webhooks never had: appointments, services, venues, class passes, client class passes and products. The event catalogue lists them.

Invoice webhooks

invoice.created is sent for every new invoice, reverse invoices included. invoice.updated is sent for every change to an invoice or to its line items. A change of the company's currency alone sends nothing: the next invoice delivery carries it. Each delivery carries the invoice's current state, read when the delivery is prepared, not the change that triggered it. See Invoice events for what triggers each event.

Invoice fields

The table maps each field of the Back Office invoice payload (data.object, sent by invoice-finalize, invoice-pay, invoice-dispute and invoice-revert) to the Public API invoice in data. Amounts change unit: Back Office amounts are integers in minor units (4900), Public API amounts are decimal strings in major units ("49.00"), line items included.

Back Office data.object field Public API data field Note
id id Same invoice UUID.
status status Same values.
invoice_number legal_identifier
invoice_type invoice_type Same values. 1 is a reverse invoice.
source_invoice_id source_invoice_id Same invoice UUID: on a reverse invoice, the invoice it refunds. Null on every other invoice and on the reverse invoices of partial pass refunds, which are not linked to an invoice.
date_created created_at
date_issued issued_at Null on drafts, voided invoices, and invoices without a recorded issue date (older and imported invoices). The Back Office payload sent the invoice date in that case.
date_due None It always held the invoice date, not a payment deadline. issued_at gives that date once the invoice is issued; while issued_at is null, no field replaces it.
date_cancelled canceled_at
currency currency
total, amount_due total_amount Both held the same amount.
amount_paid paid_amount
None pending_amount, outstanding_amount Payments still processing, and what the client still owes.
customer client_id customer.id was the client's user account ID, not the client ID. Get the name, email, phone, address and official document ID with GET /api/v2/management/clients/{client_id}/.
company company_id The object held the company's own name and address.
line_items line_items Same structure (line_count, data), amounts in major units.
invoice_pdf invoice_pdf Same link.
receipt_pdf None
object, data.date None Use the envelope date.

Detect invoice lifecycle steps

The Public API has no finalize, pay, dispute or revert event. Store the last status and issued_at you received for each invoice id, and compare each invoice.created and invoice.updated delivery with them:

  • On invoice.created, compare with a null status and issued_at: an invoice created, issued and paid in one sale arrives already paid.
  • On invoice.updated for an invoice you have no state for, store its state without treating it as a step: it is an existing invoice that changed. To start from a baseline instead, load the invoices you track with GET /api/v2/management/invoices/ before you switch.

The table gives the change that matches each Back Office event.

Back Office event Detect it when
invoice-finalize issued_at goes from null to a date. status is open, or already paid when nothing was due.
invoice-pay status becomes paid. outstanding_amount is "0.00".
invoice-dispute status goes from paid back to open.
invoice-revert status becomes refunded (an issued invoice) or voided (a draft), and canceled_at is set. A refund also creates a reverse invoice: you receive invoice.created with invoice_type 1 and a negative total_amount.

One delivery can show two steps at once, for example an invoice finalized and paid in the same operation. Handle each step it shows. A step can also be skipped: each delivery carries the state when it is prepared, so an invoice paid and refunded within seconds can arrive already refunded. A refunded invoice keeps its paid_amount: above "0.00" means money was received before the refund.

Compare with the state you stored, not with the event

Most invoice.updated deliveries change neither status nor issued_at: a line item was added, or a product was renamed. Several changes close together can also produce several deliveries that all carry the latest state. Act on a step only when the delivered state differs from the state you stored, and deduplicate on X-Bsport-Webhook-Delivery-Id.

Billing-plan webhooks

No Public API webhook replaces billing_plan-create and billing_plan-update. They stop when Back Office webhooks are switched off. To follow client memberships, read them with GET /api/v2/management/client-subscriptions/: see the API reference.

Migrate a webhook

Follow these steps for each Back Office webhook. Keep the Back Office webhook active until the last step.

1. List your Back Office webhooks

Open the Legacy webhooks table of Settings > Webhooks & APIs in the Back Office (Settings > Webhook in the previous Back Office). Note each event and destination URL, and the fields your receiver reads from each payload.

2. Update your receiver

Give Public API deliveries their own URL, so each endpoint parses one payload format. For each Public API delivery:

  1. Verify Bsport-Signature against the raw request body with the registration's signing_secret, as described in Verify the signature.
  2. Read X-Bsport-Webhook-Payload-Version (2026-10-06 for these registrations) and alert on a value your receiver does not know: see Payload evolution.
  3. Skip deliveries whose X-Bsport-Webhook-Delivery-Id you already processed.
  4. Read data with the event mapping above.
  5. Return a 2xx status once the delivery is stored. A 5xx status or a timeout is retried; other 4xx statuses are not.

3. Create the registration

Create one registration per event. You need a company-manager Public API key.

curl --request POST \
  --url 'https://YOUR_ENV_DOMAIN/api/v2/management/webhook-registrations/' \
  --header 'X-Api-Key: YOUR_API_KEY' \
  --header 'X-Client-ID: YOUR_CLIENT_ID' \
  --header 'X-Company-ID: YOUR_COMPANY_ID' \
  --header 'Content-Type: application/json' \
  --data '{
    "event_type": "booking.updated",
    "url": "https://integrations.example.com/webhooks/bsport-v2",
    "is_active": true
  }'

Replace YOUR_ENV_DOMAIN with public.staging.bsport.io (staging) or public.production.bsport.io (production). See Environments.

The response includes the registration id and its signing_secret. Store the secret as a secret: your receiver needs it to verify every delivery. Without an API key, create the webhook in the Webhooks tab of Settings > Webhooks & APIs and copy its signing secret there. Update to V2 on a webhook in the Legacy webhooks table opens that form with its URL and Public API event: change the URL to your Public API endpoint.

4. Send a test delivery

Send one signed sample delivery to the registration's URL:

curl --request POST \
  --url 'https://YOUR_ENV_DOMAIN/api/v2/management/webhook-registrations/REGISTRATION_ID/test/' \
  --header 'X-Api-Key: YOUR_API_KEY' \
  --header 'X-Client-ID: YOUR_CLIENT_ID' \
  --header 'X-Company-ID: YOUR_COMPANY_ID'

is_delivered is true when your endpoint answered with a 2xx status. The sample data holds placeholder values, such as 0 for IDs, so the test checks the signature and parsing, not your business logic. See Test a registration.

5. Run both webhooks for a short check

Leave the Back Office webhook active while the Public API webhook delivers to its new URL. Both report the same changes, so let only one of them act: keep processing Back Office deliveries, and log and compare the Public API ones.

Switch processing to the Public API deliveries once you have received each event you subscribe to and its data matches what the Back Office webhook sent. Around the switch, one change can reach you from both webhooks, one before and one after it. Storing the state by resource ID absorbs that. When a delivery triggers an action, such as an email or an accounting entry, key the action on the resource ID and the state it records, not on the delivery, so the change acts once.

To roll back before the next step, send {"is_active": false} to the registration with PATCH and keep processing Back Office deliveries.

6. Turn off the Back Office webhook

Open the Legacy webhooks table of Settings > Webhooks & APIs and choose Turn off for the webhook. In the previous Back Office, delete it in Settings > Webhook.

Turning off cannot be undone

Turning off deletes the Back Office webhook. Do it only once your receiver processes Public API deliveries.

The previous Back Office payload pages remain reachable for existing integrations, but they are no longer part of the primary documentation navigation: