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 anullstatus andissued_at: an invoice created, issued and paid in one sale arrives alreadypaid. - On
invoice.updatedfor 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 withGET /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:
- Verify
Bsport-Signatureagainst the raw request body with the registration'ssigning_secret, as described in Verify the signature. - Read
X-Bsport-Webhook-Payload-Version(2026-10-06for these registrations) and alert on a value your receiver does not know: see Payload evolution. - Skip deliveries whose
X-Bsport-Webhook-Delivery-Idyou already processed. - Read
datawith the event mapping above. - Return a
2xxstatus once the delivery is stored. A5xxstatus or a timeout is retried; other4xxstatuses 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.
Legacy reference links¶
The previous Back Office payload pages remain reachable for existing integrations, but they are no longer part of the primary documentation navigation: