Skip to main content
This is the hand-over page for a server-to-server integration: your Salesforce org (or the automation in front of it) pushes Leads into a Yotel campaign via the public API, Yotel’s voice agent calls them, and Yotel writes the outcome back as a Call_Detail__c record on the Lead.
This is a different integration from the Yotel for Salesforce managed package, which is a Lightning Web Component for a human to click “Push to Yotel” from inside the CRM. This page covers the API-driven path — no LWC, no browser click, your own automation calls the REST API directly (or bulk-pushes a report/list view export on a schedule).

1. Mint an API key

  1. Log into app.yotel.in as a tenant admin (api_keys:manage permission).
  2. Settings → API Keys (app.yotel.in/settings/api-keys) → Generate Key.
  3. Pick the live environment. For scopes, either leave everything unticked (full access) or tick at least leads:write — the scope this integration pushes leads with.
  4. Copy the yt_live_… value immediately — it’s shown once; Yotel stores only a hash.
Use a yt_test_… key first if you want to validate the integration without dialing the PSTN — see Quickstart and Authentication.

2. Push leads

Push a Salesforce Lead using its own field names — Yotel accepts lead_id / first_name / mobile as aliases for our canonical salesforce_lead_id / name / phone, so there’s no field-mapping step on your side. Full reference: Leads.
This is the same endpoint the Leads reference documents, called with the CRM spellings. The two pages are not two APIs. Either spelling works on every request; if you send both, the canonical name wins. Responses always use the canonical names.
Send Idempotency-Key on every push, single or bulk. It’s the correct tool for a retry of the same request — a batch export job that times out and resends the identical payload gets back the original response, no reprocessing, no duplicate. It is not a dedup mechanism for two genuinely different pushes of the same lead — see below for that. Details: Idempotency.
lead_id (salesforce_lead_id) does two jobs beyond round-tripping to Call_Detail__c.Lead__c later: it’s a second dedup key alongside the phone number, so a re-push of the same Lead with a corrected number is still recognized as the same lead rather than double-dialed, and it’s how Yotel resolves the Lead when writing the call outcome back (§4). Single vs. bulk handle a duplicate differently. A single push (POST /leads) that matches an existing lead in the campaign — by phone hash or by salesforce_lead_id, either is enough — is rejected outright with 409 Conflict:
id there is the id of the existing Yotel lead (not the one you just tried to push); salesforce_lead_id is its existing SF id, or null. No phone number appears in the request or the 409 body.
Do not join on lead_id here. In a request body — and in every bulk results[] row — lead_id is your Salesforce Lead id. In this 409 body it is Yotel’s lead id. Read id for the Yotel lead and salesforce_lead_id for yours; lead_id is kept only for backwards compatibility. See Leads.
A bulk push (POST /leads:bulk) never 409s for this reason — a duplicate row is counted in duplicates and reported as {"lead_id": ..., "phone": ..., "status": "duplicate"} in results alongside every other row, so one bad row in a 500-lead file doesn’t fail the batch. Full shapes for both: Leads.

3. What happens on the call

Once the lead is pushed, Yotel’s dialer places the call and bridges the answered leg into the configured voice agent. name (first_name) you pushed is forwarded automatically as a {{lead_name}} prompt variable — so if your agent’s greeting template says “Hi , this is…”, the bot greets the caller by the name you sent, no extra config needed. salesforce_lead_id is forwarded the same way, in case your agent’s prompt or tool-calls need it mid-conversation.
Personalization is additive: if you also push "metadata": {"variables": {...}} with your own keys, those are forwarded too and take precedence over the lead_name / salesforce_lead_id defaults for any key you set explicitly.

4. Call outcomes in Salesforce

When the call ends, Yotel writes a Call_Detail__c record (create, or a PATCH if one already exists for that call — writeback is idempotent, so a retried delivery never duplicates the record):
No phone number is ever written to Call_Detail__c. The field map is an explicit allowlist — Yotel does not have your customer’s number to write even if it wanted to (see the privacy section below).
The one thing your Salesforce admin needs to do: grant the connected app’s integration user Read, Create, and Edit on Call_Detail__c — not Create alone — plus edit field-level security on the fields above. Yotel needs all three because writeback isn’t a one-shot create: before writing, it queries Call_Detail__c by Call_ID__c to check whether a record already exists for this call (idempotency — Read), and after the initial create it PATCHes the same record once the recording finishes processing, typically a minute or two later (Recording_File__c — Edit), plus on any retry of a transient failure (Edit again). Read on Lead is also required, but that’s standard — your integration user already has it. Yotel’s account team configures the connected-app credentials on our side during onboarding — the SF-side grant above is the only action item on yours.
Create-only is not enough. With Create but not Edit, the initial Call_Detail__c record is written successfully, but the follow-up recording-link PATCH fails with Salesforce’s INSUFFICIENT_ACCESS error — every call outcome shows up in Salesforce, permanently missing its recording link, and the failure is easy to miss because the first write looked fine. Grant Edit up front rather than debugging this later.
Until the full grant is in place, writeback attempts fail visibly (not silently): they show up as failed jobs your Yotel account team can see, and the call itself, the recording, and the call.ended webhook (§5) are completely unaffected — call outcomes just don’t reach Salesforce until the permission lands.

5. Subscribe to call outcomes

If you want to write your own records instead of (or in addition to) relying on the Call_Detail__c writeback, subscribe to the webhooks directly:
  • call.ended — fires the moment a call finishes, before the recording is ready. Carries call_id, salesforce_lead_id, status, hangup_cause, durations, disposition, and a recording object (available: false at this point).
  • call.disposition_set — fires when the behavioural outcome is recorded (Interested, Site Visit, Do Not Call), which is what lands in Sub_Dispoisition__c. It also fires again on a later correction, carrying previous_disposition — so if you keep your own copy of the outcome, this is the event that keeps it right rather than leaving you with the first answer forever.
  • call.recording_ready — fires 30–120s later with recording.available: true and the same non-expiring recording.url that Yotel writes to Recording_File__c.
Which of these reach you, and which of them contribute to the Call_Detail__c writeback, is configurable per campaign — see Event routing. Full payload shapes: Event catalog. Set up the subscription at Developer → Webhooks and verify deliveries with Signature verification — every delivery is signed so you can confirm it came from Yotel before trusting the body.
call.ended carries no phone number and no lead name — join on lead_id or salesforce_lead_id against your own Lead record for contact details. This is deliberate: it’s what keeps the event safe to log and safe to deliver regardless of your tenant’s privacy mode.

6. Privacy

If your tenant is configured for strict PII handling (the default we set up for a Salesforce-integrated tenant, since the number’s system of record is your CRM, not ours):
  • The phone number is stored encrypted at rest from the moment you push it. It’s decrypted in memory only for the instant it takes to place the call, and never written back to the database in plaintext.
  • Every API response you receive — the single-push response, every row of a bulk response, GET/list calls — echoes a masked number (••••3210) instead of the plaintext, for the lifetime of the lead.
  • The number is purged from the database entirely — enforced at the database layer, not by a code path that could be skipped. A lead that opts out (DND) is purged the moment it does. A lead that finishes (completed, or exhausted its retries) keeps its number for the tenant’s retention window (default 30 days, configurable per tenant) so the campaign can be re-churned or cloned, and is purged by a nightly sweep after that.
  • The number is never written to application logs, on any call path.
  • The voice agent receives the number for the duration of the call only, so it can send a WhatsApp message to the person it is speaking with. The Zetta voice agent holds it in memory, never stores it, and masks it (last 4 digits) in every log and in its post-call report. For personalization it uses lead_name / salesforce_lead_id (§3), not the number.
This is not configurable per-request — it’s a tenant-level setting Yotel sets up during onboarding, so every integration on your tenant gets the same guarantee automatically.