About
Goliath fires an outbound HTTPPOST to a URL you configure whenever either
of the following runs against a contact:
- A Send Webhook step in a Contact workflow (see the workflows editor).
- The Forward Contact tool, invoked by a text agent when the contact is ready to be handed off downstream (the tool’s target URLs live on the Communication Agent’s config — the agent cannot choose them).
trigger_source differs. Every configured URL receives the same JSON
payload; there is no per-URL transformation.
Triggers
The webhook fires from exactly one of two places in Goliath. The payload is identical;trigger_source is how your receiver can tell them apart.
Workflow node (WORKFLOW_NODE)
A Send Webhook step running inside a Contact workflow. URLs are configured
on the node itself in the workflow editor — one step can hit multiple URLs.
The node fires the instant the workflow executor reaches it; there is no
debounce, no batching across contacts, and no queue between the workflow and
the dispatcher.
Dry-run previews of a workflow skip the HTTP dispatch entirely but still
record an audit entry — safe to use when validating a workflow graph without
hitting your real endpoints.
Agent tool (AGENT_TOOL)
The Forward Contact tool, called by a Communication Agent during a live
conversation when the agent’s LLM decides the contact is ready to be handed
off. The URLs live on the agent’s configuration — the model chooses whether
to call the tool, but not where the payload goes.
If the bound agent has no forwarding URLs configured, the tool returns a
no-op { status: 'failed', urlsFired: 0 } without dispatching and without
throwing. The agent sees the failure in the tool response and can explain
it to the user.
Delivery semantics
Headers
Goliath sends only one header:User-Agent, no request signing, no HMAC, and no API-key
header. See Authentication below for how to secure your
receiver.
URL sequencing
When a step has multiple URLs configured, they are hit sequentially in configured order, not in parallel. Each URL has its own 10-second timeout. The dispatcher is fail-fast: the first URL to return a non-2xx response (or time out, or fail at the socket level) aborts the entire dispatch, and any remaining URLs in the list are not attempted. Order your list so the most critical receiver comes first.Error handling
- Any non-2xx response is treated as a failure. 4xx and 5xx are not distinguished — both fail the step identically.
- The response body is read and included in the recorded error message (truncated to the first 300 characters) to help you debug from the workflow audit UI.
- For
WORKFLOW_NODE: the node is marked failed and any failure branch you have wired in the workflow takes over. The error message is visible in the workflow’s run history. - For
AGENT_TOOL: the failure surfaces as a failed tool invocation in the agent’s conversation transcript. The agent’s LLM sees the error and decides how to recover (retry the tool, apologize to the user, etc.).
No automatic retries
A failed request is not retried by Goliath. If you need at-least-once semantics, wrap your receiver in your own queue (accept the request, 202 back, process asynchronously on your side) and make the internal processing idempotent oncontact_id + triggered_at.
Receiver guidance
Success criteria
Any2xx status code is a success. The response body is not parsed —
there is no way to return structured data that affects the workflow (no
“skip next step”, no “override a field”). Dispatch is one-way, fire-and-forget.
Authentication
Because Goliath does not sign requests, you are responsible for any receiver-side authentication. Common patterns:- Embed a long random secret in the URL as a query string
(
https://your.service/hook?key=...) and validate it server-side. The URL is stored encrypted at rest in Goliath and only transmitted over TLS. - IP-allowlist Goliath’s egress range at your load balancer.
- Terminate the webhook at a lightweight proxy (API Gateway, Cloudflare Worker) that checks the shared secret and re-signs the request for your internal services.
Idempotency
The paircontact_id + triggered_at uniquely identifies a dispatch. Use
it as your dedup key. Note that a single contact can fire multiple webhooks
per day — from different workflows, or from multiple agent handoffs — so
contact_id alone is not sufficient if you care about per-event semantics.
Payload shape
Top-level fields:| Field | Type | Description |
|---|---|---|
id, contact_id | string | Goliath’s stable contact UUID. Both fields are the same value — id is provided for Zapier-style dedup keys. |
contact_name | string | Title-cased full name. Empty string if not set. |
primary_phone | string | null | Top-ranked phone number as a flat string. |
primary_phone_type | string | null | MOBILE, RESIDENTIAL, or UNKNOWN. |
primary_email | string | null | Top-ranked email. |
primary_address | string | null | Resolved street address for the first mapped property, or null if none. |
phone_numbers | array | All verified-or-unverified phones. See below. |
emails | array | All verified-or-unverified emails. See below. |
properties | array | Properties associated with this contact. |
tags | array | All tags and custom-field values, fully resolved to human-readable labels. |
contributors | array | Assigned users on this contact. |
seller_intent_score | number | null | Goliath’s internal 0–1 seller-intent score. Derived as the highest lead score across any properties currently mapped to this contact; null if no properties are mapped to the contact or if the score has not been computed yet. |
created_at | string | ISO-8601 timestamp when the contact was first created in Goliath. |
updated_at | string | ISO-8601 timestamp of the most recent write to the contact record. |
calls | array | Up to the 10 most recent calls, each with a condensed transcript. |
texts | array | Up to the 20 most recent SMS messages, newest first. |
notes | array | Up to the 20 most recent visible notes, newest first. Hidden and deleted notes are omitted. |
deals | array | All non-archived deals this contact is on. |
triggered_at | string | ISO-8601 timestamp when the dispatch was prepared. |
trigger_source | "WORKFLOW_NODE" | "AGENT_TOOL" | What caused the webhook to fire. |
phone_numbers[]
phone_typeis one ofMOBILE,RESIDENTIAL,UNKNOWN.verification_statusis one of:VERIFIED— a user has explicitly confirmed this number is correct.UNVERIFIED— the default; added from skip-trace, manual entry, or an upstream integration and never confirmed.WRONG— a user has explicitly marked this number as incorrect. It’s kept on the contact so future skip-trace runs don’t re-add it.
rankingis the display order on the contact record (1 = primary). May benullwhen no ranking has been assigned.- The array is sorted by
rankingascending, withnullrankings last.primary_phoneandprimary_phone_typemirror the first ranked entry.
emails[]
verification_status uses the same VERIFIED / UNVERIFIED / WRONG
semantics as phones. The array is sorted by ranking ascending and
primary_email mirrors the first ranked entry.
properties[]
tags[]
Every tag — including list memberships, free-form markers, and custom
fields — is flattened into a single array of {tag_name, tag_type, value}
tuples. For dropdown custom fields, value is the resolved option label
(not the option ID).
tag_type is one of: LIST, FREE_FORM, CUSTOM_FIELD_DROPDOWN,
CUSTOM_FIELD_TEXT, CUSTOM_FIELD_NUMBER, CUSTOM_FIELD_DATE,
CUSTOM_FIELD_DOLLAR.
contributors[]
roleisPOINT_PERSON(the contact’s owner) orPARTICIPANT.- Internally Goliath tracks a distinct
OWNERrole that is serialized toPOINT_PERSONon the wire — receivers never need to handle it separately. - Revoked contributors (users previously assigned and then removed) are filtered out; only active contributors are included.
calls[]
Up to 10 most recent calls, newest first. Each entry:
direction:INBOUNDorOUTBOUND.status:RINGING,COMPLETED, orNO_ANSWER.recording_url: signed URL to the source recording, ornullif no recording was captured. Internalgs://URIs are never exposed.transcript: dialogue as an ordered array of{speaker, text}pairs. Per-utterance timing is intentionally omitted to keep bodies small — fetch the Call via the GraphQL API if you need start/end offsets.transcriptisnullwhen no transcription has been produced yet.
texts[]
Up to 20 most recent SMS messages exchanged with this contact, newest first.
Each entry:
direction:INBOUNDorOUTBOUND.status:PENDING— queued inside Goliath, not yet handed to Twilio.SENT— Twilio accepted the send.DELIVERED— delivery confirmed by Twilio (or, for inbound messages, the state in which Goliath records them).FAILED— Twilio returned an error or the carrier rejected the message.SKIPPED— Goliath refused to send because the recipient has explicitly opted out of texts (e.g., repliedSTOP). No message was transmitted. Only applies to outbound messages.
delivered_at: ISO-8601 timestamp when Twilio confirmed delivery.nulluntil then — including while the message is still pending, has failed, or was skipped.created_at: ISO-8601 timestamp when the message was recorded by Goliath.
notes[]
Up to 20 most recent visible notes, newest first. Hidden (internal-only) and
deleted notes are excluded. Each entry:
is_pinned: whether the note is pinned to the top of the contact in Goliath.- Note replies are not included — fetch the Note via the GraphQL API if you need the full thread.
deals[]
All non-archived deals this contact is on, most recent close date first:
stage: the human-readable pipeline stage name (not the UUID).price_cents: price in USD cents, serialized as a string so large values survive JSON parsing without loss of precision.nullwhen no price is set.close_date: ISO-8601 timestamp, ornull.