Introduction
Brosbots offers a flexible architecture for integrating with external services. You configure integrations once at the organization level, then enable them in individual agents. The catalogue in the Integrations tab is one continuous grid of tiles. You narrow it with the search box, the category filter and the All / Connected / Not connected filter. The agent tools - transfer, end call and your own HTTP API - sit at the front of the grid, because nearly every agent needs them; see Tools. Next to the catalogue is the Custom tab: a wizard for systems the catalogue does not cover. It builds calls to your API during the conversation, event webhooks and lead addresses for your forms; see Custom integrations and Webhooks.Integration categories
Phone numbers
Numbers are managed by the platform - you buy them and assign them to agents in the dashboard, in the Numbers tab. You don’t need an account with an external carrier.Calendar and bookings
In-call bookings for appointment-based businesses (salons, clinics, restaurants).Google Calendar and Outlook
The agent sees free time in your calendar and books into it - you sign in once, no API keys
Cal.com bookings
Let callers book appointments directly during the call via Cal.com
Leads and callbacks
The agent can call a new customer on its own: someone who filled in the website form or a Google Ads lead form, left a number in a Facebook, LinkedIn or TikTok ad (Make and Zapier templates), showed up as a new contact in HubSpot or Pipedrive, or could not get through to your number.CRM and automations
During or after a call, the agent can: save leads to HubSpot, Pipedrive or Daktela, send follow-up SMS and emails, notify on Slack, trigger Zapier, run an n8n workflow or a Make scenario (and hear what they answer), and send webhooks to any system (your own backend). It can also read: a Google Sheets lookup during the call answers questions from a spreadsheet you already keep - see Reading a sheet during the call. It can also check something nobody wrote down for it - see Searching the web mid-call. A shop’s own orders and stock are read from BaseLinker - see BaseLinker: orders and stock - from Shopify, see Shopify: orders and product availability, from WooCommerce, see WooCommerce: orders and product availability - or straight from an Allegro account, see Allegro: orders and offers. A Slack notification, an email, a Zapier trigger and a calendar entry can also happen while the caller is still on the line - see Slack, email, Zapier and the calendar during the call. You’ll find the configuration in the dashboard under the Integrations tab and in the settings of individual agents.The after-call SMS
The message is written in the agent’s Integrations tab. The field has an Insert variable menu listing what this particular agent collects during a call, the call’s own facts ({{caller_number}}, {{call_date}},
{{call_time}}, {{call_duration}}, {{call_disposition}}, {{agent_name}})
and what is known before the call - so no name has to be remembered. Below the box you see how many paid SMS the text costs, and a warning once it
needs more than one - the carrier bills each. A single SMS is 160 characters,
but one non-GSM character cuts that to 70, which is otherwise something you
learn from the invoice. The counter measures the template; filling the
variables in makes it longer.
10 SMS an hour from a plfon number
The plfon carrier accepts 10 SMS an hour from one number. The rest are not lost: the platform keeps to the limit itself and queues the overflow, and an SMS goes out once the number may send again (within about an hour). On the SMS tab such a message carries a Queued badge with the time it goes out, and the call screen shows it as “queued”. Once an hour per number you get a notification (bell and e-mail, as you set them) that SMS are waiting. The SMS is charged when it is sent, not when it is queued. If the recipient repliesSTOP in the meantime, or lands on the
Do not call list, the queued SMS
does not go out and is marked “not sent”.
plfon sends no delivery reports, so the last status of an SMS from a plfon
number is sent: the carrier accepted it. Whether the phone received it is
not known. SMS from Telnyx numbers get delivered or not delivered from
the carrier.
SMS to a fixed number (alert)
The other direction: a message to you, not to the caller. In the same section turn on SMS to a fixed number, give a number (several separated by a comma) and write the alert - e.g.Call at {{call_time}} from {{caller_number}}, outcome: {{call_disposition}}. You also choose when it goes out: on every
attempt, on answered calls only, or on unanswered ones only.
{{call_summary_short}} - a summary trimmed to 140 characters - resolves in
the alert only. The alert is sent from the agent’s number and billed like any
other SMS, so several recipients means several messages.
Suggest a text reads the agent’s system prompt and proposes a few ready
messages for different call goals - confirming what was agreed, a summary with
the next step, a follow-up after an unanswered call. Every suggestion fits in
one SMS - longer drafts are dropped before you see them. A suggestion only enters
the field when you pick it, and you can still edit it. Suggestions use only
variables that will actually resolve.
Google Sheets and the calendars (Google, Outlook, Cal.com) work on the
Free plan - your agent writes every call into your own spreadsheet and
books appointments without paying for anything. Email, Zapier, n8n, Make
and webhooks start on the Start plan, Slack and WhatsApp on Pro. The CRM
integrations - HubSpot, Pipedrive and Daktela - start on
Business.
Finding an integration
The Integrations page has a search box next to the category tabs and an All / Connected / Not connected filter. Search matches the provider name, the description and the category label, so “calendar” or “sheet” finds the right tile without knowing the brand. While either is active the category headings are dropped and the matches render as one grid. The same pair - search, and an All / On / Off filter - sits above the action cards in an agent’s own Integrations tab. Cards that are off sit two to a row; the one you switch on spans the full width, where its settings appear.Saving checks the connection
Save connection verifies the credentials with the provider before it writes anything. If the check fails nothing is saved, and the form tells you exactly what to fix - a truncated private key, say, or a spreadsheet that was never shared with the service account. The usual Google Sheets mistake is how the private key was copied out of the JSON file. Literal\n, wrapping quotes, a trailing comma, even the whole JSON
file pasted into the key field are all handled for you - but a key that was cut
short is refused at save time rather than on the first real call.
If you know the check cannot pass yet (the spreadsheet gets shared later, for
instance), use Save anyway.
Save anyway does not get past a Google service account key that cannot be
read at all (damaged, cut short, not a private key): the connection is not
saved and the form says what is wrong with the key. Such a key never gets a
token, so saving it would only move the failure to every call. Renaming an
existing connection or changing its default tab does not check the key again.
What each check proves
Slack, Zapier, n8n, Make and webhooks have no read side, so the only way to check them is
to deliver something. Saving a new connection, or a changed URL, puts one test
message in the channel; renaming a connection sends nothing.
Connecting HubSpot
HubSpot has retired its old API keys, so the connection uses a private app token. Create the app in HubSpot under Settings > Integrations > Private Apps, and on the Scopes tab tick all three of these:
Save the app, copy its access token (it starts with
pat-) and paste it into
the connection.
Save connection reads the token’s scopes from HubSpot and names the ones
that are missing, so a token that can only read contacts is refused here rather
than at the end of the first real call. Adding a scope later needs no new token
- tick it in HubSpot, save the app, then press Test connection.
Field mappings after a call
CRM and Google Sheets write data after a call using the mappings you set in the agent configuration, in the Integrations tab. Each mapping has three parts:
You don’t have to retype the names - click the arrow in the Source
field to see the variables available for the chosen context (
gathered
ones come with the description of what the agent finds out). The list of
collected data lives in the Variables tab - see
Template variables.
None of these names has to be remembered. The arrow in Source lists the
variables available in the chosen context, and the arrow in CRM field lists
the real contact fields of your HubSpot, Pipedrive or Daktela, labels and custom
properties included. Match the CRM’s fields fills in everything that can be
recognised by name or label in one go. A field name one character off is
accepted by the form and dropped by the CRM after the call, in silence - which
is why it is worth picking from the list.
A new contact, or an update to an existing one
The HubSpot write never creates a second contact for somebody already in the CRM. Before anything is created, the contact is looked up in this order:- Phone number - first as the call delivered it, then in E.164 form
(
+48537666648) and as bare digits, so a number typed into HubSpot with spaces or dashes is still found. Thephone,mobilephoneandhs_calculated_phone_numberproperties are all searched, the last being HubSpot’s own normalised copy. - Email address - only once the number has found nobody, or when the call has no caller id at all (the website widget).
Matching needs the
crm.objects.contacts.read scope. A token without it still
writes, but cannot recognise an existing contact and creates a new one every
time.The call on the contact’s timeline
Writing properties says what changed, not what the call was about. So after the contact is saved, a call activity is added to it - the one HubSpot shows under Recent activities on the contact record:- the call summary,
- the next steps from the action-item list,
- the outcome, sentiment, topics and the agent’s name,
- a link to the recording,
- the duration and direction, in the fields HubSpot displays itself.
When the write fires
Every after-call card has two filters:- When to send - After every call attempt (including no answer) or Only when the call actually happened. No answer and busy only occur on outbound campaigns; on inbound calls the two options mean the same thing. In a campaign with retries the first option records several entries for the same number.
- Only on a chosen call outcome - the list holds the outcomes this agent has already recorded on real calls (“appointment booked”, “not interested”). They are not tools or script steps. An outcome that has not happened yet can be typed in by hand.
Call analysis data (the call context)
Everything you see in the call history - the summary, action items, intents,
sentiment - can be written to a spreadsheet or a CRM like any other field. Pick
the call context and the picker offers:
You will find the call id in the app in the ID column of the call history,
and in the header of the call itself, where clicking it copies it to the
clipboard.
The summary and analysis are produced in the background after the call ends.
When a mapping uses them the platform waits for the result before writing,
so the columns do not arrive empty. When no mapping uses them the analysis is
not forced and costs you nothing.
Google Sheets
Once a connection is selected the platform reads the spreadsheet itself, and the mapping form becomes the sheet: one line per real column, each with a single choice - what goes in it, or nothing. There is no column name to type, so there is none to get wrong. Add a column in the spreadsheet and Refresh columns brings it in; Match the sheet’s columns fills every line it can work out from the column names in one go. Columns are matched to what the agent collects by name or by the question behind them, so a column headedemail or Adres e-mail finds the same
variable. A mapping left pointing at a column the sheet does not have is
flagged rather than dropped. Those mappings collect in a “columns are missing
from the sheet” box, where each one shows the header to add (with a copy
button - Copy all headers copies them tab-separated, to paste into the
sheet’s first row in one go) and, in words, what will land in it - e.g. “What
lands here: Call date (Call summary). This is a system variable - the platform
works it out after the call.” Add the header to the sheet, click Refresh
columns, and the mapping joins the ordinary column list.
If the read fails (wrong service account or spreadsheet ID, or the sheet is
not shared with the service account), the fields still work as plain text -
type the names manually.
How a row finds its columns
The mappings are a list, and by default the row is appended in that order: the first mapping lands in column A, the second in B, and so on. The column name you type is then only a label for you. That breaks the moment the sheet is not in the same order - an inserted column, two swapped, or a sheet built by hand from the variable names in the Variables tab. So the platform reads the header row first and, when it recognises every titled column, writes each value under its own header instead, in any order. A column is recognised when its header is:- the column name given in a mapping, or
- the source of a mapping (
name,email,call_date, …), or - the name of a variable the call collected - a column headed
jaki_ma_wolumen_polaczenfills itself, no mapping needed.
Mappings are not created from your variables automatically after the agent
exists. The default list is built once, when the agent is created, from the
fields chosen in the wizard. Add a variable later and it reaches the sheet
either by adding a mapping for it, or by heading a column with its exact name.
Signing in with Google
The simplest way to connect a spreadsheet is to sign in: Integrations → Google Sheets → Sign in with Google, and pick the account that can edit the spreadsheet. Right after you come back from Google, a list of that account’s spreadsheets opens - choose the one the agent should write to, and its name shows next to the connection. No Google Cloud project to create and nothing to share. BrosBots can open only the spreadsheets chosen in that list, not your whole Google Drive. That is why a spreadsheet is chosen with Choose spreadsheet rather than by pasting its address: a pasted address works only for a spreadsheet that was chosen from the list before. The same button is:- next to the connection under Integrations → Google Sheets (the default spreadsheet),
- in the agent editor: Integrations → the spreadsheet tile, the note or the lookup → This agent’s spreadsheet (another spreadsheet for this agent; empty = the connection’s),
- in the agent wizard next to the spreadsheet address,
- in a new campaign with a Google Sheet source.
A connection signed in before the spreadsheet list existed says “This
connection was made before spreadsheets could be chosen from a list”. Click
Sign in again and choose the spreadsheets from the list once more - on the
connection and in every agent that has its own spreadsheet.
Share the spreadsheet with the service account
For a service account connection made before sign-in existed. A service account is a Google user of its own, and it starts with access to nothing. Creating the key is not enough: open the spreadsheet, click Share, paste the Service Account Email (...@....iam.gserviceaccount.com) and give
it the Editor role.
This is the step that is missed most often, and until it is done every write is
refused with 403 PERMISSION_DENIED.
Taking a note during the call
Save a note to Google Sheets, in the agent’s Integrations tab, appends a row while the caller is still on the line - a callback request, a message to pass on, anything someone has to act on later. It uses the same connection as the post-call push.
The columns also decide what the agent asks for: map one to the caller’s name
and the tool grows an argument the model has to fill; leave it out and it never
asks. The date, the time and the caller’s own number fill themselves from the
call (a model asked for the time invents one). The row is laid out by the
sheet’s own headers, so reordering columns in Google Sheets breaks nothing, and
a column that is configured but missing from the tab is reported back to the
agent and the log instead of vanishing silently.
Reading a sheet during the call
Everything above writes. Look up row in Google Sheets, in the agent’s Integrations tab, is the other direction: the agent reads the spreadsheet while the caller is still on the line - an order status, a booking, a customer record, a price. It uses the same connection as the write, so there is nothing new to authorise. Until this existed the only way round it was to stand an n8n or Make webhook in front of the same spreadsheet, because a custom HTTP tool cannot mint a Google service-account token on its own. That still works, and is still the answer for anything a plain row lookup cannot do (writing back, joining two sheets, filtering on more than one column) - but for “find the row and tell me what is in it” the webhook is no longer needed.What to configure
Fill in What this sheet holds. Switching the tool on is not enough on its
own - an agent that has not been told the sheet exists answers “I don’t have
that information” instead of looking. This is the field that makes it check.
How the row is found
Row 1 is the header row. Every row below it is data, and the agent searches for the value the caller gave - an order number, an email, a code - across every column, or in one named column if it asks for one. The matching row comes back as column name / value pairs, so the agent can answer with any field in it. Two things are deliberately forgiving:- Case and spacing are ignored, so
ANNA KOWALSKAfindsAnna Kowalska. - Phone numbers match on their last nine digits. Caller ID arrives as
+48601234567, and the sheet has whatever the person who typed it felt like -601-234-567,0048 601 234 567. All of those find the same row.
Limits
A spreadsheet has no query engine, so the lookup reads the tab and scans it. That is fine for the sheets people actually run agents against - up to a few thousand rows - and each lookup adds roughly half a second to the call. The scan stops at 5000 rows. Past that the agent is told the sheet was only read in part rather than being allowed to answer “not found” from half of it. A sheet that large is a sign the data belongs in a database, with the agent reaching it through an HTTP API tool.Slack, email, Zapier and the calendar during the call
Six actions in the agent’s Integrations tab run while the caller is still on the line: Slack notification, Send email, Zapier trigger, n8n workflow, Make scenario and Schedule meeting. You set them up the way you set up the note - besides picking a connection, you tell the agent when to use the action and what it should say. The n8n workflow and the Make scenario are the only ones that can answer: whatever they answer, the agent passes on to the caller - see n8n and Make.
The call does not hang up until an action started in the same turn has
finished, so a Slack note the agent fires off as the caller says goodbye is not
cut off halfway.
A Slack notification or an email sent during the call is recorded in the
Integrations panel of that call, just like the post-call pushes - so a
message the agent never sent and one Slack rejected show up as different things.
The post-call notification is a separate integration, configured in the same
tab (summary, outcome and recording to a channel). Both can run at once: the
in-call action raises what is happening on the line, the post-call one sums it
up.
Who the agent may text, WhatsApp and e-mail
The model picks the recipient of an SMS, a WhatsApp message, a payment link and an e-mail sent during the call. So that a text uploaded to the knowledge base, a page found on the web or the caller themselves cannot talk the agent into sending somewhere else, two rules apply:- Recipient. SMS, WhatsApp and payment links go only to the caller’s number and to numbers listed in Other allowed recipients. An e-mail goes only to the Default recipient, the addresses on the same list and the contact’s address from the call data (a lead, a campaign contact). Any other recipient is refused, and the agent tells the caller the message did not go out.
- Per-call limit. At most 3 SMS (payment links included), 3 WhatsApp messages and 3 e-mails in one call.
Agents set up before 19 Oct 2026 that e-mailed an address the caller gave
need this switch after the change - otherwise the e-mail is refused.
When nothing was written
Two places answer this, and they answer different questions:- A specific call - open it in the call history. The Integrations panel next to the transcript lists what each integration did with that call, and why it did nothing when it wrote no row. A successful write names where it landed (“Row written to ‘Leads’ (row 42)”), and Show what was sent underneath expands every value that went into each column or CRM field - the same way the call’s own contexts are shown above it.
- An integration that is switched on but left no outcome at all is flagged in amber. It means the after-call job never reached it (an error, or it ran out of time) - not that nothing was configured.
- The connection in general - Recent deliveries on the Integrations page lists the whole organization’s attempts, newest first.
Retries and the Retry button
The HubSpot, Pipedrive, Daktela and Google Sheets pushes retry on their own when the vendor is briefly unavailable: an answer429 (too many requests),
500, 502, 503, 504, a connection that could not be opened, or a read
that timed out. Each request is tried at most three times, a few seconds
apart; a Retry-After longer than 10 seconds is not waited for. When the push
needed more than one attempt, the call’s Integrations panel says how many.
Errors that will not fix themselves - a revoked key (401), missing
permissions (403), data the CRM rejects (400) - are not retried. Neither is
a contact or row write that timed out halfway: it may have landed, and sending
it again would make a duplicate.
A push that still failed has a Retry button next to it in the call’s
Integrations panel. It runs that one push again - not the SMS, WhatsApp, Slack
or webhooks of the same call - and the outcome appears as a new row in the
panel a few seconds later. The CRM push looks the contact up first, so a
retry updates the contact an earlier attempt may have created rather than
adding a second one. The button is only on the newest outcome of a push: an
older failure that a later attempt already fixed cannot be repeated.
The row goes directly under the tab’s last filled row - blank rows below the
data (which is what a sheet built on Google Tables, or one from an import,
looks like) are skipped. It used to go through Google’s own “append”, which on
such a sheet lands past the table’s whole reserved area, hundreds of rows below
the header. If older calls are missing from under the header, scroll down: they
are most likely there.
Browser (WebRTC) calls write rows exactly like phone calls do. The only
difference is
call.phone_number, which stays empty because there is no number
to record.Searching the web mid-call
Everything else the agent knows is something you put there: the prompt, the uploaded documents, a spreadsheet. That is fine for prices and opening hours and useless for anything that changed this week - which application windows are open, what a public register says today. Search the web, on the agent’s Integrations tab, is the one tool whose answer is not in our database. Underneath it is the Brave Search API: a paid API licensed to return results, answering in one round trip. The free plan covers the odd lookup per call.Setting it up
- Go to brave.com/search/api, register and pick a plan.
- Copy the subscription token from the API Keys tab.
- In the app: Integrations → Brave Search → paste the key. Optionally
set a country (
PL) and query language (pl) to narrow results to your market. - On the agent: Integrations tab → Search the web → pick the connection.
Leave What it should look up empty and the agent still searches, but only
when it decides it does not know. The field moves that line: it tells the agent
which subjects it should not trust its own memory on.
What the agent gets
A title, a description snippet and the source, up to three results. Page bodies are deliberately not fetched: reading and summarising three articles is seconds of silence on a phone call, and the answer has to fit in two sentences anyway. The agent reads out the gist, says where it comes from, and offers to text or email the link if you have those tools switched on. A failed search does not derail the call. An exhausted quota, a rejected key and a timeout all come back as a sentence the agent says out loud (“I could not look that up”), rather than as silence or an invented answer.AgentMail: an address of the agent’s own
Sending over SMTP and reading over IMAP both need a server, a port and an app password. That is the most common thing to get wrong when setting an agent up, and it cannot be checked until something fails. AgentMail replaces those credentials with one API key and gives the agent an address of its own.Setting it up
- Go to agentmail.to, register and copy the API key. The free plan is three inboxes and 3,000 messages a month.
- In the app: Integrations → AgentMail → paste the key. Leave the inbox
field empty: saving creates an address for the agent on
agentmail.to, or reuses the one the key already has. - A custom domain (
agent@yourcompany.com) is added in the AgentMail dashboard, along with its DKIM, SPF and DMARC records.
What changes
On the agent’s Integrations tab the e-mail tool’s connection picker now
lists both SMTP and AgentMail connections - the agent sends through whichever
one you choose.
A briefing with no IMAP
The Daily briefing section gained a Where to read mail from field: your own mailbox over IMAP, or the agent’s inbox. With the agent’s inbox you set a forwarding rule in your own mail (Gmail: Settings → Forwarding) and the briefing reads whatever arrives - without giving us any password of yours.AgentMail does not read your Gmail. It hands out new inboxes, so existing
mail reaches the agent by forwarding. Reading a Google account directly means
either IMAP with an app password or an OAuth app with Google’s annual security
assessment - hence the choice.
When the test says the key was rejected
Test connection checks the key against your inbox list first, so “AgentMail rejected this API key” now means the key, and nothing else. Two other answers are about the Inbox field instead:
If the key really is refused, check it from a terminal - this is the same call
the test makes:
200 means the key is fine and the problem is the inbox. 401 means AgentMail
does not know the key: generate a new one in the AgentMail dashboard and paste
it without the surrounding spaces.
Online stores
The agent answers the two questions shop customers call about: “where is my order” and “do you have it in stock”. It reads the answer live from the system you sell through:- BaseLinker - order status, tracking number, and stock and prices from your BaseLinker catalogue.
- Shopify - fulfillment status, tracking number and variant availability through a custom app in your own admin.
- WooCommerce - orders and product availability from a WordPress shop through read-only REST API keys.
- Allegro - orders and active offers from your seller account, connected with a single sign-in and no key to paste.