API: Synchronize bookings with CRM and other systems
Regular REST synchronization keeps bookings up to date in a CRM, internal overview or reporting system. It can also supplement webhooks. This guide shows how to retrieve all new bookings, handle repeated processing and account for later changes to bookings already known.
Prepare synchronization
- Set up your own API key with read permissions for the required resources. Use the API: First steps for authentication and headers.
- Define which data the target system requires: Booking ID, offer, booking person, waitlist status, and cancellation status are a possible starting point.
- Permanently store the assignment between edoobox booking and target data record as well as the last fully processed period in your integration.
- Define two runs: a frequent retrieval of new bookings and a regular synchronization of already known bookings.
The following queries use GET /v2/booking/list. Host, API key, token, and edid must belong to the same environment and to the selected account. The API-v2-reference contains the endpoints and the downloadable OpenAPI file.
Distinguish booking, participants, and timestamp
| Field | Use in synchronization |
|---|---|
id | Stable assignment of the booking data record in the selected account. Store as an opaque value unchanged; do not derive an internal database number from it. |
time | Booking time. This allows you to limit bookings by their creation time. |
offer | Reference to the booked offer. |
owner | Reference to the booking person. A booking can contain further participants. |
canceled | Cancellation indicator of the booking. Adopt the delivered current value in the target system. |
waiting_list | Waitlist indicator. Consider separately from the cancellation indicator for evaluation. |
time is not a modification timestamp. A booking created earlier and changed or cancelled today is not automatically found by a filter for bookings created today. The booking timestamp also does not indicate when the booked offer starts.
For participant lists or individual items, the booking person alone is not sufficient. If necessary, load the associated participants or booking details with the resources provided for this purpose. The functional distinction is explained in Booking detail.
Choose a fixed time window for new bookings
Use a fixed lower and upper limit for each run. An example is time >= 2026-10-16T00:00:00Z and time < 2026-10-17T00:00:00Z. This makes it clear which run processes a booking at the day boundary. Pass date values as an ISO-8601 string with time zone, for example Z for UTC or an explicit offset. Convert returned date values for comparisons to the same time zone.
The time filter is set with filter, the field time and expression. The operators used here are >= and <. The generally visible parameters from and to are no substitute for the shown field filter for this booking list.
Overlap consecutive retrievals slightly if necessary, for example by five minutes. This is a decision of your integration. Repeatedly read bookings must be updated based on their ID. An overlap does not replace the synchronization of older bookings.
Query the first page with filter, fields, and sorting
The following server-side JavaScript example builds a read request for the fixed time window. edid and accessToken come from your successful authentication. URLSearchParams handles the URL encoding of the JSON parameters.
const url = new URL('https://app2.edoobox.com/v2/booking/list');
url.searchParams.set('fields', JSON.stringify([
'id', 'time', 'offer', 'owner', 'canceled', 'waiting_list'
]));
url.searchParams.set('filter', JSON.stringify([
{ property: 'time', expression: '>=', value: '2026-10-16T00:00:00Z' },
{ property: 'time', expression: '<', value: '2026-10-17T00:00:00Z' }
]));
url.searchParams.set('order', JSON.stringify([
{ property: 'time', value: 'ASC' },
{ property: 'id', value: 'ASC' }
]));
url.searchParams.set('limit', JSON.stringify({ start: 0, reply: 100 }));
url.searchParams.set('response_type', 'array');
const response = await fetch(url, {
headers: { edid, Authorization: `Bearer ${accessToken}` }
});
if (!response.ok) throw new Error('Could not load the booking page');
const page = await response.json();
if (page.error || !Array.isArray(page.data) || !page.limit) {
throw new Error('Invalid or rejected booking response');
}
// Only now process page.data in the target system.
// Read further pages with the same filter and an adjusted limit.start.
fields is a JSON list of field names, filter a JSON list of conditions, and order a JSON list of sort fields. The sort direction is in value. Multiple conditions of this simple filter list are linked with AND. Retain filter and sorting when changing the page.
Sorting by time and then id also uniquely orders bookings with the same timestamp. Without explicit sorting, the general list uses the ID in ascending order.
Process all pages until the complete result
A list query delivers a maximum of 100 entries by default; the maximum requested page size is 2000. limit.start is a start position from 0, limit.reply the page size. For the second page with 100 entries, use {"start":100,"reply":100}.
- Check the HTTP status,
error, the expected data range, and the page data for each page. - Process the bookings from
data. Withresponse_type=arrayyou receive a list; by default, the data range is an object with booking IDs as keys. - Read the confirmed values
limit.start,limit.replyandlimit.totalof the response.totalrefers to the hit set of the current query. - Calculate the next position from
start + reply. Request it with the same filters as long as it is smaller thantotal. - If you unexpectedly receive an empty page although further hits are reported, or contradictory page data, abort the run for inspection.
A failed page does not count as a successful end. The page data describe the respective retrieval; the sequence of multiple requests is not a guaranteed immutable snapshot. Therefore, retain the fixed time window, the unique sorting, reproducibility, and the complementary inventory synchronization.
Master duplicate processing and interruptions
Store exactly one assignment to the target data record per environment, account, and booking ID. If the same booking is read again, update this data record. Use a unique assignment in the target system for this, so that two simultaneously arriving processes do not create a second booking.
- Read the last fully completed processing status from your own database.
- Set the upper time limit for the new run once and use it on all pages.
- Read and process all pages. Save progress and errors so that an interrupted run can be safely repeated.
- Advance the persisted completion checkpoint to the upper boundary only after every page has been processed and every import has succeeded.
If page 3 fails, for example, retain the previous completed checkpoint. Repeat the run with the same boundaries; records with IDs already imported are updated. Design the import into the destination system so that fetching the same data again does not send another confirmation, create another CRM activity or trigger other duplicate effects.
Synchronize changes and cancellations of older bookings
Plan an additional regular synchronization of the inventory that your target system still requires. Load the stored booking IDs again via GET /v2/booking/{id} or read the required inventory completely via the paginated booking list. Adopt the current fields instead of just appending new IDs.
Do not exclude cancelled bookings across the board during synchronization: A filter on exclusively active bookings can otherwise prevent a previously active target data record from being set to cancelled. Additionally, separate booking status, waitlist, individual participant positions, and payments functionally. The cancellation indicator alone does not prove a completed refund.
If a previously known booking is missing, check the account, permissions, filter, and the targeted single retrieval before triggering a deletion or cancellation in the target system. Keep unclear cases for processing. The frequency and the considered inventory depend on your functional need and the available API capacities.
Combine Webhooks with the REST synchronization
The guide for setting up and operating Webhooks leads through API access, topics, reception, signature check, post-control, and deactivation. Use it together with the REST synchronization described here.
For bookings, for example, the topics post_Booking for creation and put_Booking for booking changes exist. Check the actually configured topics and the received events for your process. This does not imply a guarantee that every conceivable functional status change generates a specific event.
Run Webhook processing and REST synchronization on the same stored booking assignment. An event can trigger a targeted retrieval of the affected booking. Process updates of the same ID in a coordinated manner and read its current state so that a later processed older event does not overwrite a newer state.
Plan the synchronization also for duplicate, delayed, or missing notifications. A strictly once-only delivery, fixed repetition intervals, or a specific delivery order are not guaranteed with this guide. The regular inventory synchronization remains part of your integration logic.
Application example: Booking overview in the CRM
An organization wants to display new course bookings in the CRM and adopt later cancellations. The integration performs, for example, a retrieval of new bookings every 15 minutes. It starts five minutes before the last completed processing status and uses an upper limit set at the start of the run. Both intervals are freely chosen operating values.
- The initial setup reads the intended historical booking inventory via all pages and stores each booking ID with its CRM assignment.
- The ongoing time windows supplement new bookings. Overlaps update existing assignments.
- A suitable Webhook triggers a targeted retrieval and the same update in the CRM.
- A daily inventory synchronization reads the still required older bookings again. This also considers a record cancelled today whose booking time is several weeks in the past.
- A failed run generates a processable error with timestamp and step. The completed processing status is only advanced after a successful repetition.
The result is a current booking overview with a traceable synchronization status. This integration does not generate a new booking in edoobox nor a refund by reading. For payment reconciliations, additionally choose the appropriate invoice and transaction resources.
Narrow down common synchronization errors
| Observation | Check |
|---|---|
| Always only 100 bookings | Evaluate page data and read further start positions; the first page is only an excerpt. |
| Old cancellations are missing | Check the creation time filter and read already known bookings again. |
| Duplicate CRM data records | Check the unique assignment from environment, account, and booking ID as well as simultaneous processing. |
| Bookings at the day boundary are missing | Check time zones, the inclusive lower boundary, the exclusive upper boundary and the last fully completed checkpoint. |
| Data range cannot be processed as a list | Compare response_type and expected response format; the default object is divided by IDs. |
| Access breaks off during the run | Check token lifetime, key status, and read permissions; fix errors and repeat from the secured status. |
Log the run, the time window, the page position, and the error category. Tokens, secret keys, and complete personal booking responses do not belong in freely accessible logs.