Docs
Autodial API & Webhooks

Autodial API & Webhooks

Send a lead from any system and Asisto calls them. Full reference for the autodial endpoint, authentication, signing, and inbound HubSpot webhooks.

Send Asisto a phone number and it calls that person, following the rules your business has configured. Anything that can make a web request can do it: your website, a form builder, Zapier, Make, n8n, or your own backend.

Quick start#

  1. Go to Settings, Organization, Developers and select Create API key. Give it a description you will recognise later, such as the name of the tool that will use it. Copy the key when it appears, because it is shown once.
  2. Post a phone number to the endpoint below.
  3. Watch the call happen on the Outreach page. If nothing arrives, the key's entry on the Developers page still reads "Never used", which means the request never reached us.

Send "dryRun": true while you are wiring this up and the whole sequence is planned and recorded without anybody's phone ringing.

curl -X POST https://www.asisto.one/api/v1/autodial \
  -H "Authorization: Bearer api_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "+14155550111",
    "name": "Dana Reed",
    "email": "dana@example.com",
    "consent": {
      "given": true,
      "source": "web-form",
      "evidence": "https://example.com/contact-form"
    },
    "idempotencyKey": "form-submission-8842"
  }'
bash

Authentication#

Send your API key as a bearer token:

Authorization: Bearer api_your_key_here

Some low-code tools cannot set an Authorization header. Those may send the key as X-Api-Key instead. Both are equivalent.

Scopes#

Every request is checked against the scope the endpoint needs.

ScopeAllows
autodial:writeEnrolling a contact, which starts calls
autodial:readReading the status of an enrollment
contacts:readReading contact records
contacts:writeCreating and updating contact records

Keys issued from the dashboard carry all four, and so do keys created before scopes existed. There is no scope picker yet, so nothing you create needs configuring before it works, create a key and it can enrol contacts.

Because every key can do everything, create one key per tool rather than sharing one. Revoking a key takes effect on the next request, so a key you no longer recognise costs nothing to revoke.

Expiry and revocation#

A key either never expires or expires at the end of the day you pick, so a key set to expire on 4 March works for all of the 4th.

Revoking a key from the Developers page stops it immediately. It disappears from your list but is kept internally, because a key that started calls is part of the record of why those people were telephoned.

A missing key, an expired key and a revoked key all return the same 401 with the same message. This is deliberate: distinguishing them would let anybody probe which keys are real.

POST /api/v1/autodial#

Enrols a contact and schedules the first call. Returns immediately with 202; the call itself happens in the background.

Request body#

FieldTypeRequiredNotes
phonestringyesAny readable format. Without a + prefix it is read as a number in your own country
namestringnoUsed to greet them and saved to the contact
emailstringnoSaved to the contact
consentobjectusuallyRequired unless the consent rule is turned off for the business
consent.givenbooleanyes, within consentMust be true to satisfy the rule
consent.sourcestringnoWhere consent came from, such as web-form
consent.evidencestringnoA URL, form id, or anything you could produce if challenged
consent.externalRefstringnoThe record id in your own system
consent.capturedAtISO 8601noWhen consent was given. Defaults to now
businessIdUUIDonly with several businessesWhich business is calling
campaignstringnoWhich sequence to run, if you have more than one
scheduledAtISO 8601noEarliest time to start. Still held to the calling window
idempotencyKeystringnoSend the same key twice and the second request changes nothing
metadataobjectnoAnything you want the AI to know about this person
dryRunbooleannoPlan and record the whole sequence without placing a real call

Two metadata keys are read rather than simply stored:

  • timeZone or timezone: an IANA timezone such as America/Denver. Given one, the calling window uses it instead of inferring a zone from the area code, and the hour of safety margin at each end is dropped.

Successful response#

{
  "id": "9f2c8a1e-4d3b-4c7a-9f21-6b0d5e8c7a44",
  "status": "accepted",
  "deduplicated": false,
  "firstAttemptAt": "2026-09-02T18:00:00.000Z",
  "statusUrl": "/api/v1/autodial/9f2c8a1e-4d3b-4c7a-9f21-6b0d5e8c7a44"
}
json

firstAttemptAt may be later than you asked for. If the contact is in a timezone where it is 3am, or your business is closed, the call waits for the next legal moment. deduplicated is true when an idempotency key matched an existing enrollment, in which case nothing new was created.

Status codes#

CodeMeaning
202Accepted. The first call is scheduled
400The number is unreadable, a field is invalid, or a businessId is needed
401The API key is missing, expired, revoked, or the signature failed
403The key does not carry the autodial:write scope
404No such business, or the business is not yours
409Already enrolled, called recently, or on your do-not-call list
422No consent record, and this business requires one
429Rate limited, or a daily limit is reached

Every refusal carries a reason you can branch on, so your own system can tell a business rule from a fault:

{
  "error": "That contact is already being called.",
  "reason": "already-enrolled",
  "enrollmentId": "9f2c8a1e-4d3b-4c7a-9f21-6b0d5e8c7a44"
}
json

Possible reasons: invalid-phone, unreachable-destination, business-not-found, no-sequence, sequence-disabled, sequence-paused, no-caller-id, consent-missing, suppressed, already-enrolled, cooldown, daily-cap, daily-contact-cap, reserved-sequence, duplicate-request.

GET /api/v1/autodial/{id}#

Reports what has been tried and what is next. Requires autodial:read.

curl https://www.asisto.one/api/v1/autodial/9f2c8a1e-4d3b-4c7a-9f21-6b0d5e8c7a44 \
  -H "Authorization: Bearer api_your_key_here"
bash
{
  "id": "9f2c8a1e-4d3b-4c7a-9f21-6b0d5e8c7a44",
  "status": "active",
  "phone": "+14155550111",
  "name": "Dana Reed",
  "source": "webhook",
  "createdAt": "2026-09-02T18:00:00.000Z",
  "endedAt": null,
  "endedReason": null,
  "dryRun": false,
  "calls": { "attempted": 1, "remaining": 2, "perDay": 3 },
  "messages": { "sent": 1, "remaining": 2, "perDay": 3 },
  "attempts": [
    {
      "kind": "call",
      "attempt": 1,
      "outcome": "no_answer",
      "wasScreened": false,
      "talkSeconds": 0,
      "at": "2026-09-02T18:00:04.000Z",
      "detail": null
    },
    {
      "kind": "sms",
      "attempt": 1,
      "outcome": "sms_sent",
      "wasScreened": false,
      "talkSeconds": null,
      "at": "2026-09-02T18:00:40.000Z",
      "detail": null
    }
  ],
  "next": {
    "kind": "call",
    "scheduledAt": "2026-09-02T18:20:00.000Z",
    "heldForDailyLimit": false,
    "deferredForQuietHours": false
  }
}
json

remaining is what is left of this enrollment's ladder; perDay is how much the contact may receive in any twenty-four hours. The two are different numbers, and next says which of them moved the step: heldForDailyLimit means the contact has had their allowance for today, and deferredForQuietHours means the time was outside the calling window. Neither is an error, the call is still going to happen.

An enrollment id from another organization returns 404 rather than 403, so the endpoint cannot be used to confirm that an id exists.

Outcomes#

OutcomeMeaning
connectedSpoke with the contact
screened_connectedReached them after answering call screening
bookedAn appointment was booked on the call
no_answerNobody picked up, or they hung up within seconds
busyThe line was busy
voicemail_leftAn answering machine received the message
machine_no_messageAn answering machine, message not left
wrong_numberReached somebody else. Number suppressed
dead_numberNot in service. Number suppressed
opted_outAsked not to be called. Number suppressed
sms_sentA follow-up text went out
sms_failedA text could not be sent
skippedThe step was not taken, with the reason in detail
failedThe attempt failed for a technical reason

Signing requests#

Optional, and off unless your key has a signing secret. There is no control for this in the dashboard yet, so unless somebody has set a secret on your key for you, nothing below applies and the bearer token over HTTPS is the whole contract.

When a key does have a signing secret, its requests must carry two extra headers, and requests without them are refused:

HeaderValue
X-Asisto-TimestampCurrent Unix time in seconds
X-Asisto-SignatureHMAC-SHA256(timestamp + "." + rawBody), hex encoded

The timestamp is inside the signed material rather than beside it, so a captured request cannot have its window extended by rewriting it. Requests older than five minutes are refused.

const crypto = require('crypto');
 
const body = JSON.stringify({ phone: '+14155550111' });
const timestamp = Math.floor(Date.now() / 1000).toString();
const signature = crypto
  .createHmac('sha256', process.env.ASISTO_SIGNING_SECRET)
  .update(`${timestamp}.${body}`)
  .digest('hex');
 
await fetch('https://www.asisto.one/api/v1/autodial', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.ASISTO_API_KEY}`,
    'Content-Type': 'application/json',
    'X-Asisto-Timestamp': timestamp,
    'X-Asisto-Signature': signature
  },
  body
});
js

Sign the exact bytes you send. Re-serialising parsed JSON changes the whitespace and breaks an otherwise correct signature.

Rate limits#

Accepted enrollments are limited to 60 per key per minute. Over the limit, the endpoint returns 429 with a Retry-After header in seconds. Refused requests do not count against it, because a refusal costs nothing and an accepted one telephones somebody.

Separately, each business has a daily limit on new contacts. That limit also returns 429, with reason: "daily-cap", and exists so that a bulk import into your CRM does not turn into a calling list.

There is one more, per contact rather than per business: a number that has already had its day's allowance of calls is refused with reason: "daily-contact-cap", also as 429. Like the others it is a pace rather than a verdict, the same request tomorrow is accepted.

Connecting without code#

Any tool that posts JSON works. In Zapier, Make or n8n, add a Webhooks or HTTP Request step with:

  • Method: POST
  • URL: https://www.asisto.one/api/v1/autodial
  • Headers: Authorization: Bearer api_your_key_here and Content-Type: application/json
  • Body: JSON containing at least phone

Map your form's phone field to phone, and set idempotencyKey to the submission id so a replayed run does not call anybody twice.

HubSpot#

HubSpot does not use this endpoint. Connect it from Settings, Organization, Integrations and Asisto subscribes to contact creation directly, which is faster than any polling loop and needs no configuration on your side.

  • New contacts are picked up within seconds, with a sixty-second backfill poll catching anything a delivery missed.
  • Only contacts matching your enrollment filter are called. By default that is a lead-stage contact with a phone number created in the last day, which is deliberately narrow so connecting your CRM never turns your whole customer list into a calling list.
  • Mobile numbers are preferred over landlines.
  • Every attempt is written back to the contact as a logged call, along with properties for the last outcome, the attempt count and the last call time.
  • Disconnecting stops new contacts being picked up. Sequences already running finish, because those people were already told they would be called.

Outbound webhooks#

Separately from the autodial endpoint, Asisto can call your URL when something happens. Configure these under Settings, Organization, Developers.

Every delivery is a POST with a JSON body:

{
  "trigger": "contactCreated",
  "createdAt": "2026-09-02T18:00:00.000Z",
  "payload": {}
}
json

If you set a secret on the webhook, the body is signed with HMAC-SHA256 and sent as X-Webhook-Signature-256. Verify it by computing the same HMAC over the raw request body and comparing in constant time. Without a secret the header reads no-secret-provided, which you should treat as unsigned.

Deliveries are retried up to three times on failure. Respond 2xx quickly and do the work afterwards, rather than holding the connection open.

Testing safely#

Send "dryRun": true and the entire sequence is planned, scheduled and recorded without a single real call or text. It appears on the Outreach page marked as a dry run, which makes it a genuine rehearsal rather than a mock.

Ready to get started?

Try our AI receptionist free and see the difference it makes for your business.