BE VOICEby Binary Elements

Integrations for agents

Campaigns from your system

A campaign can take its list of people to call straight from your system at the start of each run, and send each person's result back as soon as it's known. Nobody exports spreadsheets, and your system stays the record of who was called and what they said.

Your system or an uploaded list

A list I upload (CSV)
Someone uploads a spreadsheet on the campaign's page. Each run calls that list as it was when the run started. Nothing to build: see Upload a contact list. Results stay in BE Voice (the run's page and its CSV), and can also be sent to your system with Also send each result to.
My system sends the list (connection)
At the start of each run, BE Voice asks your system who to call, then sends each result, and a summary at the end, back to it. You build two endpoints, described on this page.

Both use the same campaign settings: the AI agent, the number calls come from, the schedule, calling hours, attempts and rules. See Set up a campaign.

How to set it up

  1. Add a connection to your system in Settings → Integrations (see How to add a connection).
  2. Open Campaigns → + New campaign, or click a campaign and change its settings.
  3. Under Who to call, set The list to My system sends the list (connection).
  4. Choose the Connection, and set the List path and Results path on your API (by default /campaign/list and /campaign/results).
  5. Save, then try a test run to your own phone before turning the campaign on.
Who to call: The list set to My system sends the list (connection), Connection Harbour jobs system, List path /campaign/list, Results path /campaign/results
The campaign asks the Harbour jobs system for its list.

Both endpoints are called with POST on the connection's address, with its sign-in header and BE Voice's signature (X-BE-Voice-Timestamp, X-BE-Voice-Signature), exactly as for an agent's tools. Check the signature as described in Checking a request came from BE Voice.

The list request

When a run starts (on the campaign's days at its start time, or when someone clicks Run now or starts a test run), BE Voice sends:

POST https://api.example.com/campaign/list
Content-Type: application/json

{
  "event": "campaign.list",
  "campaign": {"id": "cmp_ycv5…", "name": "Overdue jobs follow-up"},
  "run": {"id": "run_3k9d…", "test": false}
}

Answer 200 with the people to call today:

{
  "contacts": [
    {
      "contact_id": "C-1042",
      "name": "Jordan",
      "phone": "0400 000 000",
      "timezone": "Australia/Melbourne",
      "context": {"job_number": "HJ-1042", "balance": "$240.00", "due": "2026-10-03"}
    }
  ]
}
FieldTypeWhat it does
contact_idstring or numberRequired. Your ID for the person, up to 100 characters, unique in the list. It comes back with their result. Entries without one, or repeating one, are left out.
namestringTheir name, up to 100 characters. It's added to the context as name unless the context already has one.
phonestringNational (0400 000 000) or international (+61400000000). A number that can't be read is skipped and reported as invalid_phone.
timezonestringOptional. A time zone name such as Australia/Perth; calling hours apply in the person's own time. Default: the campaign's.
contextobjectOptional. The facts the agent works from on this call, up to 8 KB. Anything else is skipped and reported as invalid_context.
Limits
Up to 1,000 contacts per run, and an answer up to 4 MB. Answer within 30 seconds.
When the list fails
If your system can't be reached, answers with an error status, takes too long, or doesn't answer with {"contacts": [...]}, the run fails and calls nobody. The run's page shows why, and the run summary (below) carries the reason in run.error.
Before each call
Numbers on your do-not-call list are skipped (do_not_call), and the campaign's calling hours and contact limits are checked for each person.

How contacts and context reach the agent

Each contact's context becomes the facts for their call. The agent can use them in conversation, fill its greeting with them (for example {{context.name}}), and send them to your API through its tools without asking the person. Put in the IDs your tools will need, such as a job or invoice number, so the agent never has to ask for them.

Each result

Once a person is finished for the run (they answered, or there are no attempts left, or they were skipped), BE Voice sends their result to the results path:

POST https://api.example.com/campaign/results
Content-Type: application/json

{
  "event": "campaign.result",
  "campaign": {"id": "cmp_ycv5…", "name": "Overdue jobs follow-up"},
  "run": {"id": "run_3k9d…", "test": false},
  "contact_id": "C-1042",
  "call_id": "cal_8f2k…",
  "attempts": 1,
  "status": "done",
  "outcome": "promise_to_pay",
  "details": {"amount": "240", "date": "2026-10-17"},
  "note": "Will pay on Friday when the invoice is approved.",
  "summary": "Jordan agreed to pay the $240 balance on Friday 17 October."
}
FieldTypeWhat it does
contact_idstringYour ID from the list.
call_idstringThe last call to them (cal_…). Empty if they were never called.
attemptsnumberHow many times they were called in this run.
statusstringdone (answered), no_answer (not reached, or reached voicemail), or skipped (not called).
skipped_reasonstringWith skipped: why they weren't called: do_not_call, outside_limits, outside_hours, invalid_phone, invalid_context or not_allowed.
outcomestringHow the conversation ended: one of the agent's outcomes, or voicemail when a message was left. Empty when nobody answered.
detailsobjectThe outcome's details, such as an amount or a date (text values).
notestringThe agent's note on the call.
callbackobjectWhen the person asked to be called back: at (date and time with offset) and when (as they said it).
opt_outbooleantrue when they asked not to be called again. Their number is already on your do-not-call list.
summarystringA short summary of the conversation.

Fields that don't apply are left out. Answer any 2xx to take the result; the reply body is ignored.

Answers to the questions in the agent's script aren't part of this result. They're on the run's page in the console and in its Export CSV.

The run summary

When the run is over and every result has been taken (or given up on), BE Voice sends one more request to the results path:

{
  "event": "campaign.run.finished",
  "campaign": {"id": "cmp_ycv5…", "name": "Overdue jobs follow-up"},
  "run": {"id": "run_3k9d…", "test": false, "status": "done",
          "started_at": "2026-10-10T09:00:04+11:00", "ended_at": "2026-10-10T11:42:17+11:00"},
  "contacts": {"total": 4, "done": 2, "no_answer": 1, "skipped": 1},
  "outcomes": {"promise_to_pay": 1, "voicemail": 1},
  "results_not_taken": 0
}
run.status
done, stopped (someone stopped the run) or failed. A failed run has run.error (for example, the list couldn't be fetched) and contacts.total 0.
contacts, outcomes
Totals by status and by outcome. A stopped run that still had calls open when BE Voice stopped following it counts them in contacts.unfinished.
results_not_taken
Results your system never accepted within their hour of retries.

It's sent at once for a done or failed run; for a stopped run, once its last calls are over, or two hours after the stop at the latest.

Retries and repeats

  • A result or summary that isn't answered with 2xx within 10 seconds is sent again after 1, 2, 4 and 8 minutes, then every 15 minutes, for up to an hour.
  • The same result can arrive twice (for example if your answer was lost). Use contact_id with run.id as the key for a result, and run.id for the summary, and ignore repeats.
  • The list request is not retried: a run whose list fails ends there, and the next scheduled run asks again.

Test runs

A test run (Test run on the campaign's page) still asks your system for the list, then calls only the number you type, once, with the first contact's context. Its requests have "test": true in run, and its result is sent like any other, so your system can tell it apart and leave real records alone.

Starting the AI from your system

The same integration works the other way: your system can have the AI place calls and hear how they went.

ToYour system
Have the AI call one personPlaces a call through the BE Voice API with the facts for this call. The result arrives at your webhook when the call ends. See Place an AI call.
Have it work through a listProvides the two endpoints on this page: one returns the people to call with the facts for each; the other receives each person's result, and a summary when the run is finished.
Trust what arrivesChecks the signature, and treats the call's ID (or contact_id with run.id) as the key for repeats.

Either way, put the IDs your endpoints need (a job number, an invoice number) in the facts you send with each call, so the agent never has to ask the person for them.

Still stuck? Contact us and tell us which page you were on.