Skip to main content

Overview

Brilo sends an HTTPS POST to your endpoint after every call your agent handles. The body is a single JSON object containing everything you need to record the call in your CRM: who called whom, what was said, how long it lasted, the agent’s summary, and any structured data your agent extracted during the conversation. One call produces one delivery per webhook target. If delivery fails, Brilo retries automatically (see Delivery and retries).
This page describes payload version 1.1. The payload_version field at the top of the body tells you which version you’re parsing — when we add fields in a future version, older receivers keep working.

Configuring a webhook

Each agent can have its own webhook URL. Add it from the agent builder:
  1. Open your agent in the Brilo dashboard.
  2. Go to the Actions section of the agent configuration.
  3. Add a Webhook action and paste your endpoint URL.
  4. Optionally add custom request headers (e.g. an Authorization header with a shared secret) — Brilo passes these through on every delivery.
  5. Save the agent.
Shared-secret authentication. Brilo does not currently sign webhook bodies. To verify deliveries came from your own Brilo account, set a custom header like Authorization: Bearer <your-secret> on the webhook action and check it on your endpoint.
The same agent can have multiple webhook actions configured — each one receives an independent POST per call.

When the webhook fires

Brilo sends the webhook after the call ends and after the agent’s post-call analysis has completed (summary generation, success-check evaluation, and structured-data extraction). This means transcripts and analysis fields are populated before delivery starts. The webhook fires for calls in any terminal status — including completed, failed, and blocked. For calls that ended before any conversation took place, the transcript and summary will be sparse but the envelope is otherwise identical.

Request basics


Body schema

The top level of the body is a flat JSON object with up to 16 fields. The next section gives a complete realistic example; the table below documents each field individually.

Example payload

A complete payload from a successful outbound sales call. Your endpoint will receive a body of exactly this shape.

Possible values and sub-shapes

direction

call_status

The status of the call at the moment the webhook fires. The webhook is only sent for terminal states. Non-terminal statuses (scheduled, not-scheduled, paused, queued, dialing) exist in the call lifecycle but never appear in webhook bodies — Brilo waits for the call to reach a terminal state before delivering.

TranscriptEntry

Each item in transcript_object is an object with at least role and content. Additional keys may be present depending on the LLM provider — these are forwarded as-is and are safe to ignore.
The flattened transcript string at the top of the body excludes function_call and function_result entries (so it reads like a clean human conversation). The transcript_object array includes them. If you want a complete audit trail, use transcript_object.

Contact

A row from your Brilo workspace’s contacts table, matched on the phone number on the other side of the call (the contact’s number, not your Brilo number). If multiple contacts share the same phone in the workspace, all matches are included. If no contact matches, contacts is [].

Custom fields

contacts[].custom_fields echoes back, verbatim, whatever key/values were stored on the contact — the columns from a CSV import, or the fields set when the contact was added to a campaign. This is where a CRM record ID lives when it belongs to the contact rather than to a single call.
custom_fields vs variables — pick the right one for CRM tie-back. Both can carry a record ID, and they come from different places:
  • contacts[].custom_fields — attached to the contact (CSV / campaign import). Use this when your leads were uploaded with their CRM IDs. To read a Salesforce Lead ID, map contacts[0].custom_fields.salesforce_lead_id (substitute your own key).
  • variables — supplied per call on POST /v1/call. Use this only when you place calls programmatically and pass the ID at call time.
If your contacts were imported from a CSV or campaign, the ID is in custom_fields, not variables — a receiver that only reads variables will find nothing and silently skip the record even though the ID was delivered.

Variables

Round-trips the per-call variables object you supplied when placing the call via POST /v1/call. Brilo persists the values on the call record and echoes them back here so you can correlate the webhook event with the originating record in your CRM — typically a Salesforce lead_id, a HubSpot record ID, or your own customer ID.
CRM tie-back pattern. Include your record ID (lead_id, contact_id, ticket_id, etc.) in variables when calling POST /v1/call. In your webhook receiver, read body.variables[YOUR_KEY] and use it to find the matching CRM row. The call_id Brilo returns stays useful as a per-call idempotency key on top of that.

PersonalizedCallSummary

Output of a Summary action (metadata.type = "call_summary") configured on the agent. Use this when you want a domain-specific summary (e.g. “Sales Recap”, “Support Triage Notes”) in addition to the generic summary field. The whole object is null if no Summary action is configured on the agent or if it failed to run.

CallTag

Output of a Success Check action (metadata.type = "success_check") — a yes/no criterion the agent evaluates against the conversation. Only matched success checks appear in call_tags.

ExtractedField

Output of an Extract Data action (metadata.type = "extract_data") — a value the agent pulls out of the conversation (appointment date, budget, lead score, etc.). One entry per configured extractor on the agent, regardless of whether extraction found a value.
Use key (not name) as your CRM column identifier. name is user-editable and may change; key is stable as long as the action’s name doesn’t change drastically.

Receiving the webhook

A minimal receiver that parses the body, acknowledges quickly, and queues heavy work for after the response is sent.
Respond with 2xx to acknowledge receipt — your CRM write should happen after the response, not before. Anything non-2xx (including 4xx you might return for “invalid data”) will trigger Brilo’s retry policy. To reject a delivery permanently, log the issue and return 200 anyway.

Delivery and retries

Brilo delivers webhooks through a Temporal workflow with strict retry semantics. If all 5 attempts fail, the delivery is abandoned. Brilo does not currently expose a UI for listing failed deliveries — if you need to recover missed calls, you can fetch them by ID using the Get Call endpoint.
Make your receiver idempotent. Brilo’s idempotency only protects against duplicates from our retry path. If your endpoint returns 2xx but your downstream CRM write fails, you’ll want to use call_id as a unique key so a manual replay doesn’t double-write.

Edge cases & FAQ

Brilo looks up contacts by the phone number on the other side of the call (the caller for inbound, the dialled number for outbound) within the same workspace as the call. If no contact in your workspace matches that number, contacts is []. Add the contact in Brilo and future calls will populate the array.
This fallback is used when the agent’s post-call summary generation produced no usable output — usually because the call ended before any meaningful conversation took place, or because the LLM returned an empty response. The webhook is still delivered with the rest of the data.
The call ended before any utterance was captured (e.g. the recipient hung up before speaking, or the call failed to connect). The transcript_object array will also be empty in this case.
Both default to []. call_tags is empty when no Success Check actions are configured on the agent or none matched. extracted_fields is empty when no Extract Data actions are configured on the agent. If actions are configured but the conversation didn’t surface a value, individual entries will still appear with value: null.
Yes, if you’ve configured multiple webhook actions on the same agent. Each webhook action receives its own delivery for every call. Brilo’s internal idempotency prevents duplicate deliveries to the same target — but two different targets will both fire.
Brilo waits up to 10 seconds for a response. Slower responses are treated as a timeout and trigger a retry. Make your endpoint respond fast (acknowledge first, process async).
Brilo treats any non-2xx as a retryable failure and will attempt delivery up to 5 times total (the first attempt plus up to 4 retries). If your endpoint sees an “invalid” payload it can’t process, log it and return 200 to stop the retries.
We add fields additively — your receiver should ignore fields it doesn’t know about. When the schema gains new fields, payload_version will increment (e.g. "1.2"). We won’t remove fields or change types within a major version. See the Changelog.
Pass your CRM record ID as a variables entry when placing the call:
The same variables object is echoed back at the top level of the post-call webhook body, so your receiver can look up the originating record without first having to join through call_id. The field is omitted entirely when no variables were sent — guard with body.variables?.lead_id (JS) or (body.get("variables") or {}).get("lead_id") (Python). See Variables for the full contract.
Place a real test call to your agent. Brilo doesn’t currently send synthetic test deliveries — every webhook corresponds to a real call. We recommend keeping a dedicated test agent with a separate webhook URL pointing at a staging endpoint.

Changelog

v1.1 (additive update — variables echo)

Adds an additive top-level field for CRM tie-back:
  • variables — echo of the per-call variables object supplied on POST /v1/call. Omitted from the payload when no variables were sent on the originating call, so receivers pinned to the earlier v1.1 shape keep working unchanged.

v1.1

Adds three top-level analysis sections derived from the agent’s post-call actions:
  • personalized_call_summary — output of any Summary action configured on the agent
  • call_tags — matched Success Check outcomes
  • extracted_fieldsExtract Data values
Adds a payload_version field at the top of the body. Delivery now uses a Temporal-backed retry workflow: 5 attempts with exponential backoff, per-target idempotency.

v1.0

Original payload: call_id, direction, call_status, transcript, transcript_object, duration, call_from, call_to, agent_id, summary, contacts. Single-attempt, fire-and-forget delivery.