APIMCP ServerWebhooksEvent TrackerCustom Integration

Custom Integration

A guide covering BusinessChat's ETL tool, Custom Integration: scheduling syncs, enabling webhooks, and setting up live API requests against any third-party system.

Overview

Custom Integration connects BusinessChat to any third-party application and moves data between the two. Its modes:

A business can have multiple integrations. Each integration has a mode (sync, API request, or webhook) and calls an external system through a hierarchy of steps that fetch data, transform it, and insert it as custom objects or template objects.

Integration modes

An integration runs in one of three modes. The mode is what triggers it and what happens to the result.

SYNC

Pulls data in bulk and stores it in BusinessChat. It runs on a schedule (hourly, every 12 hours, or every 24 hours) or on demand. Sync is for reference data a business needs to keep on hand.

Triggered by: a schedule or a manual run. Produces: records created, updated, or deleted. One sync runs at a time.

API request

Calls the external system synchronously, the moment a bot or flow needs it, to fetch fresh data or trigger an action like creating a booking. The response comes straight back to the caller; nothing is stored. Each API request is exposed as a capability.

Triggered by: a capability call from a bot or flow. Produces: the system's live response, mapped and returned to the caller.

WEBHOOK

Provides a URL the external system can push events to.

Triggered by: the external system. Produces: records created, updated, or deleted.

Capabilities

A capability is the named operation an API request integration offers to the rest of BusinessChat. Bots and flows don't reach into an integration directly; they call a capability by its key, and BusinessChat routes it to the integration behind it. Every capability is one of two kinds: a template capability or a custom one.

Template capabilities

These are the ones BusinessChat ships as a fixed catalog. It defines GET_PRODUCT_BY_ID, GET_ORDER_BY_ID, UPDATE_ORDER_STATUS, and others (the full list is in the template reference). Their key, name, parameters, and output are already fixed; applying one drops a ready-made definition into your integration, and you fill in only how to fulfil it against your API. Because the rest of BusinessChat already understands these keys, the moment you fulfil one, existing bots and flows can use it with no extra wiring.

Custom capabilities

These are ones a business defines itself, for an operation no template covers, CREATE_BOOKING or LIST_AVAILABILITIES for a clinic, say. You give the API request integration a name and declare its parameters yourself; whatever the integration returns is the capability's output. Reach for a custom capability whenever the template catalog doesn't already have the operation you need.

Output

A capability doesn't just run, it returns something, and that return shape is part of its definition. A template capability names the object types it produces (GET_PRODUCT_BY_ID produces a Product and its Variants), and that's what filters which templates a MAP step can target. A custom capability's output is simply whatever its mapping produces. Either way, the caller gets back a mapped, predictable result rather than the provider's raw response.

Parameters

A capability carries parameters, the inputs it needs to do its job. Each parameter comes from one of two places:

Graph building

Every integration fetches its data through a graph: a hierarchy of steps that runs from the top down. Each step does one job, and steps nest so an integration can follow the shape of whatever the external system returns.

i

You need to create the entities before starting with sync.

Step types

Hierarchy

Because steps nest, a child step runs once for each record its parent produced, and it can see everything above it. Fetch all doctors, then for each doctor fetch their schedule, then map each slot: while mapping a slot, both the doctor and the slot are still in reach, so a slot's record can carry its doctor's ID.

Aliases

Each step's output carries an alias, the name later steps use to refer to it, in URLs like /doctors/{{doctor.id}}/slots and in mappings. Two aliases are always available: params (the inputs from a capability call or webhook) and steps (the results of earlier steps). A step can only reach the aliases above it in the hierarchy, not those beside it.

Pagination

When an endpoint returns its data across many pages, a FETCH step walks all of them automatically and feeds every page through the steps below it. It supports the common paging styles, cursor, offset, page number, and next-link, so a single sync brings back the full set rather than just the first page.

Testing steps

You build the graph one step at a time, and each step is tested against the live system as you go. A step can't be saved until its test passes, and because a step runs on its parent's output, a step whose parent hasn't been tested yet can't be tested either, so you configure and test top-down. Every blocking step must be in a passed state before the integration can be activated.

Good to know

Google Sheets

A spreadsheet can be an integration's external system, without writing a request by hand. Choose Google Sheets as the step's type and the step asks for a connected Google account, a spreadsheet, a tab, and what to do there; BusinessChat builds the call. Everything else works as usual: the rows a step reads carry an alias, later steps filter and map them, and a step that changes the sheet is tested before it can be saved.

Connecting a sheet

A step needs a connected Google account. Connect one once, from the step or from the connected-accounts list, and the same account is offered to every later step. Then pick the spreadsheet through Google's own file picker and the tab inside it.

What a step can do

One step does one thing. Reading works in every mode; the three that change the sheet run only in API request integrations, because they act on what a caller sends, and a sync or a webhook would repeat that on every run.

Get rows

Reads the tab and returns one record per row, keyed by your column names. Empty rows are skipped, and each row carries its row number as _row.

Runs in: any mode.

Add row

Adds one row below the last filled row of the columns it writes, filling only those columns. It returns the row it added, with its row number.

Runs in: API request. A call that gives none of its columns a value is refused rather than adding a blank row.

Update row

Changes the columns you list on rows an earlier step returned. Other columns are left alone. It returns each row with the columns it wrote.

Runs in: API request.

Clear row

Empties every cell of rows an earlier step returned. Formatting stays and the rows stay where they are, so nothing below them moves. It returns the rows as it received them.

Runs in: API request.

i

Testing a step that writes never touches the sheet. The test shows the row it would add, the rows it would change and with what, or the rows it would clear.

Columns and rows

A Get rows step needs names for its columns, because those names become the fields you map from. Two ways to give them:

You can also list rows to skip, as single rows or ranges (2, 10-12, 50-), for notes and totals that aren't records. Skipping never renumbers anything: a row keeps the _row it has in the sheet.

The steps that write name their columns by letter instead, since they don't read the sheet's headers. Each column you write also gets an alias, which is the name it comes back under in the step's result.

Choosing rows to change

Update row and Clear row don't search the sheet. They work on rows an earlier step already returned, so choosing rows is ordinary graph building:

  1. Get rows reads the tab.
  2. A Filter keeps the rows to act on, for example the booking whose id the caller sent. A Sort or an Iterate can come between.
  3. The write step picks that step's output under Rows to change (or Rows to clear) and acts on those rows.

The list offers every alias the steps above it publish. Activating the integration refuses anything that isn't rows from a Get rows step on the same tab, directly or through a Filter, Sort or Iterate, so a step can't write to row numbers that came from somewhere unrelated.

When more than one row arrives, each step has a choice: Update row changes all of them by default, while Clear row stops with an error that names the rows, because clearing the wrong rows is not something you can undo from here. Either can be switched to the other behaviour. Behind an Iterate the step sees one row at a time, so that choice never comes up.

⚠

Two things worth knowing when you write the Filter's condition:

  • It compares exactly: bk-1 is not BK-1, spaces count, and a number in a cell is not the text of that number. Wrap both sides in trim and uppercase, or the cell in toString, to match the way a person would read them.
  • A bare name in a condition is read from the row first. If a parameter has the same name as one of your column keys, the condition compares the row with itself and keeps far more rows than you meant. Rename the column key in the Get rows step to keep them apart.

The values written

Each column a step writes takes its value from one of two places: a parameter of the capability call, or a static value you type once, which is written on every call.

Good to know

Transformation

Between fetching and storing, each MAP step runs the object's mapping: one pipeline per field that turns the provider's shape into the object's fields. You build them field by field, with autocomplete and a live preview against sampled data. Paths are case-sensitive, so a field the response returns as Speciality must be mapped with that exact casing, even next to a lowercase one like qualifications.

Shortcuts

Most fields don't need any transform; a bare dotted path is a complete mapping. doctor.clinic.name extracts the value directly, resolving first against the MAP's source and falling back to any ancestor alias. Array indexing works inline (tags[0]), and typing @ suggests every alias in scope with its sampled fields.

Field operations

When a field needs more than extraction, a pipeline chains operations in order, each feeding the next. The available operations are:

Each field declares an expected type, and its final value is coerced or rejected against it. A timestamp field also declares its date format, and a value that doesn't match is caught rather than stored wrong.

Deduplication & aggregation

Reach for this only when the graph produces the same record more than once, which happens whenever a MAP sits under an iteration that repeats its subject, e.g. mapping a doctor once per branch they work at. Without aggregation you'd store the doctor several times; with it, you flag one field as the identifier (the key records are merged on) and any field marked aggregated collects its values into a distinct array across every occurrence. The result is one Doctor whose branches field lists every branch encountered. If a record only ever appears once, you don't need this.

The Operations tab

Field mappings say how a record is shaped; the Operations tab says what a sync is allowed to do with it. Each object picks its supported operations: Upsert (create or update, almost always on) and Delete. Deletes only happen if Delete is enabled here, and enabling it reveals the delete strategy, how a removal on the provider's side is detected:

Custom objects

Entities

An entity is a data model defined per business: a named type (normalized to uppercase, e.g. DOCTOR) with typed fields, text, number, boolean, timestamp (with a declared date format), enum, or array, one of which is the primary key that ties every record back to its source. A custom entity is shaped purely to match the provider's data; you define its fields yourself.

Relations

Relations link entities so synced data keeps its structure. A relation has participants (fields that each point at a specific entity and the field identifying it; a DOCTOR_BRANCH relation's participants point at Doctor and Branch) plus its own attributes for data that belongs to the link itself, like the doctor's working hours at that branch. Relations are populated by MAP steps like entities are, and related data can be traversed in flows without extra provider calls.

Template objects

Alongside the entities you define, BusinessChat ships a catalog of template objects: ready-made types with fixed fields and a fixed primary key. Applying one copies its definition into your business as a starting entity you then map to, so a Product you sync lands in the same shape the native e-commerce integrations (Shopify, Salla, Zid) use, and the rest of the platform recognises it. The e-commerce catalog covers Product, Variant, Order, Customer, Cart, Coupon, Currency, and Store URL (full field lists in the template reference). Anything the catalog doesn't cover, you model as a custom entity.

The published schema

Everything a business builds here, its entities, its relations, and its live capabilities, is published together as one schema. The schema is the contract the rest of BusinessChat reads: it's what bots and flows see and route against, and it's the only view they have of an integration. They never touch a graph or a mapping directly.

What it contains

For each business, the schema lists the stored object types and their fields, the relations between them, and the capabilities currently on offer with their parameters and output. From-entity parameters are where the two halves meet: a capability can draw an input from a stored entity's field, so create booking can take the chosen doctor's ID straight from the Doctor entity.

How it stays current

The schema republishes automatically whenever something changes it, when you add an object, activate a definition, or an integration is paused or restored. so a consumer that receives an older snapshot can discard it rather than act on stale routing.

A capability appears in the schema only while its integration is healthy. If an integration keeps failing and is paused, the capability it backs is dropped from the schema until it recovers, so bots and flows are never routed to an operation that's currently down.

Errors & recovery

Integrations run against systems you don't control, so failures are expected and handled rather than fatal. What happens depends on whose problem it is.

Who each failure goes to

Every error is classified and routed, so the right person is told and noise is kept down:

Auto-pause

A single failure doesn't stop anything. But when a sync fails repeatedly (by default, three runs in a row) the integration is paused by the system so it stops hammering a broken endpoint, and its failing step is flagged. If that integration also backs a capability, the capability is taken off offer while it's paused, so bots and flows aren't routed to something that's down. API-request integrations pause on a sustained failure rate rather than a run count.

Getting back to healthy

Recovery is deliberate, not automatic: a paused integration stays paused until you fix it. Open it, retest the flagged step (and any others still failing) until they pass, then activate. Activation re-runs the readiness check, clears the failure count, and puts any capability it backs back on offer. Health changes show up in the interface as they happen, without a refresh.

Import / export

Integrations are portable between businesses and environments. Two transfers, run in order:

Imports are validated all-or-nothing: any invalid item rejects the whole payload with each failure listed, and nothing is written. Items that already exist in the target are skipped and counted, so re-running an import is safe. Payload sizes are capped; version-stamped files guard against format drift, and a timestamp/format mismatch on a field is caught at import or blocks activation, not discovered in production.

Use it to move a tested integration from staging to production, reuse a proven setup for a new customer on the same platform, or keep a backup before risky changes. Imported definitions arrive as drafts; retest steps against the new environment's provider and activate.

Template reference

The full shipped catalog. All template capabilities are API request mode; applying any of them copies a ready-made definition into your business. Template objects and capabilities keep the same names as the native e-commerce integrations, so synced data is interchangeable with them.

Template capabilities

KeyWhat it doesOutput
GET_PRODUCT_BY_IDFetch one product by its IDProduct, Variant
GET_VARIANTSList a product's variantsVariant
GET_ORDER_BY_IDFetch one order by its IDOrder
GET_ORDER_BY_CONTACTList a contact's ordersOrder
GET_CONTACT_BY_IDFetch one customer by IDCustomer
UPDATE_ORDER_STATUSWrite a new status to an orderOrder
UPSERT_COUPONCreate or update a couponCoupon
CREATE_COUPON_CODEIssue a coupon code for a contactCoupon
GET_CURRENCYRead the store's current currencyCurrency
GET_STORE_URLRead the storefront URLStore URL

Template objects

ObjectHoldsPrimary key
ProductCatalog item: name, price, quantity, availability, categoriesproductId
VariantA product's sellable variant: SKU, price, attributesproviderVariantId
OrderOrder with amount, status, customer and shipping detailsorderId
CustomerContact: phone, name, email, language, opt-inphoneNumber
CartAbandoned cart: checkout URL, amount, customercartId
CouponDiscount: type, amount, usage limits, validity windowcode
CurrencySingle-field carrier for the store's ISO currencycurrency
Store URLSingle-field carrier for the storefront URLstoreUrl

Glossary

The core terms used across this guide, in one place.

Custom IntegrationBusinessChat's ETL tool: connects to any third-party system to sync, request, and receive data.
IntegrationOne configured connection to an external system, running in a single mode.
ModeWhat triggers an integration and what it does with the result: sync, API request, or webhook.
SyncA bulk pull, on a schedule or on demand, that stores records in BusinessChat.
API requestA synchronous call to the external system made the moment a bot or flow needs it; nothing is stored.
WebhookA URL the external system pushes events to, so records update without polling.
CapabilityA named operation an API request integration exposes to bots and flows; template or custom.
GraphThe hierarchy of steps that fetches and shapes an integration's data.
StepOne node in the graph: FETCH, ITERATE, MAP, or a webhook trigger.
AliasThe name a step's output is referenced by in later URLs and mappings.
MappingThe per-field pipelines a MAP step runs to turn a response into a stored record.
EntityA per-business object type with typed fields and a primary key.
RelationA link between entities, with participants and its own attributes.
Template objectA ready-made object type BusinessChat ships, with fixed fields and primary key.
SchemaThe published contract of a business's objects, relations, and live capabilities.