Overview
The Speed-to-Lead intake webhook is the endpoint partners and lead providers POST leads to. Each accepted lead is recorded, matched against your configured field mappings, and queued for outbound AI outreach according to your team’s scheduling rules.lead_source field, and it’s not possible to misattribute leads to the wrong source. If you operate at the portfolio or enterprise level, the routing rules below don’t apply on this URL either, since the team is already fixed by the key. Ask your Avoca contact whether a dedicated source URL exists for your integration; if not, use the shared endpoint above.
Authentication
Avoca issues your API key already in its full, final form, including theavoca_ prefix (e.g. avoca_3f1a9c2e6b7d4e2a9c3f8d5b1a2e4f6c...), when you (or the Avoca team) generate it in the dashboard. Copy it exactly as given; there’s nothing to prepend or assemble yourself. Set it as an environment variable and authenticate with either header:
Rate limits
Avoca does not currently enforce rate limits on this endpoint. There is no documented or guaranteed limit, so if you anticipate very high-volume or bursty delivery, check with your Avoca contact first.Request
Send any JSON object. Field names are resolved through your team’s configured field mappings, so you can point most lead sources at Avoca without reshaping their payloads. Standard target fields include:phone_number ← phone, Phone, contact.phone), configured per team by the Avoca team.
Fields outside this table are not lost: the complete raw JSON body you send is always stored on the lead and available in the v1 leads feed and on outbound lead events, whether or not a field mapping exists for it. Mapping only controls what gets used operationally (matching, dedupe, display), not what’s retained.
Response
Success (201 Created)
A 201 is returned whenever the request was authenticated and well-formed, whether or not a lead was actually recorded or will be contacted. Always check data.lead_id and skipped rather than treating 201 alone as “a new lead now exists.”
request_id is a UUID generated fresh per request. The example above is illustrative, not a real value to match against.
What each outcome looks like
Duplicate and repeat deliveries
Avoca treatsteam + phone_number + external_id as the identity of a lead. Re-sending a payload with the same phone number and external_id does not create a second lead or error. It updates the existing lead’s name, email, address, and notes in place, and the response reports "Duplicate lead merged into existing lead" with the original lead_id. This is safe to rely on for retries. It does not apply to correcting a phone number: changing the phone number changes the identity, so it always creates a new, separate lead rather than updating the old one.
Always send external_id when you have one. When it’s omitted, identity collapses to team + phone_number alone. Two genuinely different leads for the same phone number (e.g. two different service requests) will merge into one lead rather than creating two, since Avoca can’t otherwise tell them apart. external_id is the only thing that disambiguates repeat callers.
Forcing a repeat to be treated as new. Send bypass_deduplication: true at the top level of the payload and Avoca skips the identity match and the cooldown window for that one delivery, creating and contacting a new lead even though the same phone_number + external_id already exists. This is intended for testing your integration by replaying the same lead. Do not send it on production retries: a retry carrying it creates a duplicate lead that will be contacted again.
Edge case: occasional 500 on truly simultaneous duplicate deliveries
Edge case: occasional 500 on truly simultaneous duplicate deliveries
500 on one of them rather than both cleanly merging. If you see an occasional 500 on what should be a duplicate, a brief retry will resolve it: the underlying lookup succeeds on the next attempt.Skip and ignore reasons
skip_reason is not yet a fixed enum. Treat it as one of these categories rather than matching on exact text (only past_appointment and lead_source_disabled are stable literal strings today; the cooldown reason is a generated sentence):
201 with a normal, non-skipped response. To detect it, check is_ignored / ignored_reason on the lead via the leads feed after the fact, rather than the synchronous ingest response. The lead.created webhook event does not carry these fields (it fires at insert time, before this state is known), so the leads feed is the only place to observe it.
Errors
409 response. A payload that matches an existing lead’s identity is merged, not rejected (see Duplicate and repeat deliveries).
Example: 400 (missing required field)
Example: 400 (missing required field)
Example: 400 (invalid phone number format)
Example: 400 (invalid phone number format)
Example: 401 (missing or invalid API key)
Example: 401 (missing or invalid API key)
Examples
Minimal payload (only the required fields)
Minimal payload (only the required fields)
customer_name and phone_number are required by default.lead_source was sent, so this lead is filed under the API Webhook fallback source.Phone-only payload (accounts with customer_name made optional)
Phone-only payload (accounts with customer_name made optional)
customer_name configured as optional. Ask your Avoca contact if this applies to you before omitting it. For every other account, this payload gets a 400.Payload with extraneous, unmapped fields
Payload with extraneous, unmapped fields
internal_crm_stage, assigned_rep_id, and custom_scoring have no configured field mapping, so they don’t affect matching, dedupe, or how the lead displays in Avoca. They’re still stored verbatim on the lead’s raw payload and available through the v1 leads feed and on outbound lead events.Skipped: cooldown window
Skipped: cooldown window
data.lead_id present); it just won’t be queued for outreach. See Skip and ignore reasons.Skipped: lead source disabled (nothing stored)
Skipped: lead source disabled (nothing stored)
data.lead_id is absent. This is the one outcome where nothing was actually created, despite the 201 status.Repeat delivery (merged into an existing lead)
Repeat delivery (merged into an existing lead)
phone_number + external_id a second time (e.g. a retry, or the provider re-sending an updated record) updates the existing lead rather than creating a new one:lead_id matches the original lead. Name, email, address, and notes are updated from this request’s payload; the phone number and external_id themselves are not changed by a resend.Scheduling Behavior
When the AI calls the lead is governed by your team’s configured scheduling mode:always: call as soon as the lead arrivesbusiness_hours: call immediately during business hours; queue otherwiseafter_hours: only outside business hourscustom: custom windows configured with the Avoca team
Portfolio and Enterprise Routing
A single API key can post leads for multiple teams (every brand in an enterprise, or every team in a portfolio) through this one endpoint, instead of needing a separate key per team. This applies to bothenterprise_all_teams keys and portfolio_all_teams keys (the key type issued at the portfolio level) identically.
If you operate at the portfolio or enterprise level, ask your Avoca contact whether routing is configured for your key before assuming a routing field is optional. Your key’s anchor team is the one specific team it is directly tied to in Avoca (for a portfolio key, the team the key was generated from; for an enterprise key, whichever member team it was issued against). What happens if you omit the routing field depends entirely on whether routing is configured at all: it does not uniformly fall back to the anchor team. See below for the full breakdown.
There is no platform-wide team_id field name; which JSON field carries the target team, and how its value maps to a team, is configured once by the Avoca team on your anchor team’s Speed-to-Lead settings (routing_rules), in one of two modes:
Example: portfolio/enterprise key, team_id mode
Example: portfolio/enterprise key, team_id mode
<your_team_id> with the numeric id of the team this lead belongs to. Assumes your routing field was configured as team_id in team_id mode. The value is validated as a positive integer and must belong to a team inside your portfolio or enterprise. A team id outside it is rejected with 400 unless a fallback default team is configured for your key, in which case (same as custom_field mode below) the lead is routed to that fallback team instead and the request succeeds with 201.Example: portfolio/enterprise key, custom_field mode
Example: portfolio/enterprise key, custom_field mode
brand, mapped to a specific team on the Avoca side. Ask your Avoca contact to confirm the exact field name and its value-to-team mappings for your account.- No routing configured at all: every lead is accepted and lands on the anchor team, silently, with no error. If you’re only seeing one team’s leads and expected several, this is the first thing to check.
- Routing is configured, but this request’s value is missing, empty, or doesn’t match any rule: these are treated identically. Rejected with
400by default (Routing field value "…" did not match any configured rule, or the equivalent missing-field message), unless a fallback default team was configured, in which case the lead routes there instead. It does not fall back to the anchor team. - Value resolves to a team outside your portfolio/enterprise: rejected with
400unless a fallback default team is configured, in which case it routes to the fallback instead (same as the unmatched case above), applying in bothteam_idandcustom_fieldmodes. The one boundary that is never configurable: a fallback team itself must be inside your portfolio/enterprise, or the request is rejected regardless.
portfolio_all_teams keys is currently done directly on the anchor team’s own Speed-to-Lead settings in the Avoca dashboard; there is not yet a dedicated portfolio-wide configuration screen (enterprises have one, portfolios don’t yet). Your Avoca contact will need to know which team is your anchor team to make this change.
Tracking Leads Downstream
- Subscribe to
lead.created,lead.booking_created, andlead.completedwebhooks for real-time lifecycle updates. - Pull historical records (including your original raw payload) from the v1 leads feed:
GET /api/v1/teams/{teamId}/leads. Join onexternal_idor the returnedlead_id.