Docs/Sendify Direct and APIAPIReference

Sendify API reference

Every endpoint is open on this page with request fields, a ready to copy request, and the exact response you get back. Nothing is hidden behind a dropdown.

On this page

Start here

Jump straight to the call you need.

Copies this entire reference as clean markdown so ChatGPT or Claude can write your integration for you.

Where to find the API docs

The API docs live inside each subaccount. Create a custom location first, then open the API docs tab at the top of the portal to choose that location and edit its settings.

Where to find the API docs

Start at the agency level, create a custom location, then open the API docs tab to manage each subaccount.

1. Agency level

Log into Sendify Direct as an agency owner or admin.

2. Sub accounts

Open the Sub accounts section from the top navigation.

3. Custom location

Create a location, fill out the details, and save it.

4. API docs tab

Open API docs at the top of the portal.

5. Choose subaccount

Pick the custom location you just created.

6. Edit settings

Set webhook URL, timezone, and custom headers.

Webhook API keyIllustrative mock, portal UI may differ
SubaccountMy Custom Location
x-api-keysk_••••••••••••
Outbound webhook URLhttps://your-crm.com/hook
Copy keyRegenerate

Pick your subaccount in the key dialog, copy the sk_ key once, and use it as the x-api-key header on every call.

The two clicks that matter

  1. 1In the Sendify portal, open Agency View, then Subaccounts, and click API Docs at the top.
  2. 2Choose your subaccount in the picker. You can keep several custom locations and switch between them here.
  3. 3Copy the sk_ key for that subaccount. It becomes the x-api-key header on every request.

Your API key

Every request needs your key in the header. The key is tied to a single subaccount, so it tells Sendify which account the contact or conversation belongs to. Keep it private and rotate it from the portal if it ever leaks.

Header on every request

x-api-key: sk_your_key_here

Base URL

https://d3vzg2unix0bsb.cloudfront.net
Treat the key like a password. Anyone holding it can create contacts and start conversations in your subaccount.

Call order

Contacts come first, conversations second. The contact push is what gives you thecontactIdthat every AI call needs.

The standard call order

Every integration follows the same four steps. Skipping step two is the most common mistake.

1. Authenticate

Send x-api-key on every request. The key decides which subaccount you are writing to.

2. Push the contact

Create the contact and keep the contactId that comes back in the response.

3. Start the conversation

The AI sends the opening message to that contactId.

4. AI takes over

Replies, qualification, follow ups, and booking all run without another call.

Where contactId comes from

1

Your CRM sends a lead

Phone number is the one required field.

2

Sendify returns contactId

Save it on the record in your CRM right away.

3

Every AI call uses it

Start, stop, resume, and inbound messages all key off contactId.

Tags decide which AI agent answers

leadNew lead setter
reactivationDatabase reactivation agent
no-tagNo agent matches, conversation cannot start

Managing contacts

POST/webhooks/contacts

Add one contact, or update an existing one

Create a single contact, or update an existing one by passing their contactId. Use this when leads arrive one at a time, or when you need to change a contact's details.

On update, only the fields you send are changed. To update just the tags, send contactId and tags.
Custom field keys must match definitions configured in Settings, Contact Custom Fields. Unknown keys return a 400 error.
You can also start the AI from this endpoint instead of making 2 API calls. Rather than calling /webhooks/contacts and then /webhook/ai/startConversation, just use this endpoint, and if you have Lead Configuration set up accordingly it will fire the first initial message for you.
FieldRequiredTypeDescription
phoneOptionalstringRequired when creating a new contact. E.164 format recommended. Must be unique within your subaccount.
contactIdOptionalstringPass the contactId to update an existing contact instead of creating a new one. All other fields you provide overwrite the current values.
firstNameOptionalstringContact's first name.
lastNameOptionalstringContact's last name.
emailOptionalstringEmail address. Must be unique within your subaccount if provided.
tagsOptionalstring[]Tags controlling which AI agent handles this contact. On update, tags replace the existing list entirely.
customFieldsOptionalobjectCustom field values keyed by field key. On update, customFields shallow merges into existing values. Keys you do not send are preserved, and setting a key to null removes that value.

Request

curl -X POST https://d3vzg2unix0bsb.cloudfront.net/webhooks/contacts \
  -H "Content-Type: application/json" \
  -H "x-api-key: sk_your_key_here" \
  -d '{"phone":"+15551234567","firstName":"Jane","lastName":"Doe","email":"jane@example.com","tags":["lead"],"customFields":{"lead_source":"inbound_call"}}'

Response

{
  "status": "success",
  "message": "Contact created",
  "data": {
    "contactId": "ctc_abc123",
    "status": "created",
    "contact": {
      "id": "ctc_abc123",
      "phone": "+15551234567",
      "firstName": "Jane",
      "lastName": "Doe",
      "email": "jane@example.com",
      "tags": ["lead"],
      "customFields": { "lead_source": "inbound_call" },
      "createdAt": "2026-05-13T10:00:00.000Z"
    }
  }
}

AI conversations and messaging

Stop and start are a switch, not a message

Stop

AI stops replying and stops follow ups for that one contact. Nothing is sent.

Start

AI resumes handling the conversation. Still nothing is sent.

Start conversation

This is the only call that actually sends an opening message.

POST/webhook/ai/startConversation

Tell the AI to send its first message to a contact

Triggers the AI agent to reach out to a contact with its first message. The AI picks up from there, so you do not need to do anything else for the conversation to continue.

The contact must have at least one tag that matches an AI agent's routing rules before a conversation can start.
Push the contact first before calling this. You need the contactId.
This endpoint sends the AI's opening message. To turn the AI back on after stopping it without sending anything, use Resume the AI instead.
FieldRequiredTypeDescription
contactIdRequiredstringThe contact you want the AI to message. You get this from the create contact response.
userIdOptionalstringThe Sendify user to assign this conversation to. Defaults to the owner of the API key.
messageOptionalstringOverride the AI agent's default opening message for this contact only. Leave blank to use the agent's configured message.

Request

curl -X POST https://d3vzg2unix0bsb.cloudfront.net/webhook/ai/startConversation \
  -H "Content-Type: application/json" \
  -H "x-api-key: sk_your_key_here" \
  -d '{"contactId":"ctc_abc123","userId":"usr_xyz","message":"Hi Jane, thanks for signing up!"}'

Response

{
  "status": "success",
  "message": "Initial messages processed successfully",
  "data": {
    "success": true,
    "total_contacts": 1,
    "processed_contacts": 1,
    "failed_contacts": 0,
    "results": [
      {
        "contact_id": "ctc_abc123",
        "status": "success",
        "conversation_id": "conv_xyz789"
      }
    ]
  }
}
POST/webhook/ai/stop

Kill switch: the AI stops replying and following up with this contact

Turns the AI off for a single contact. It no longer replies to their messages or sends follow ups until you start it again. Use this when a human takes over, the contact opts out of AI handling, or your CRM flags them as do not engage.

Safe to call repeatedly. Stopping an already stopped contact returns the same result.
Fires an agent_status.changed event to your outbound webhook, if configured, so your CRM stays in sync.
Returns 404 if the contactId does not exist in your subaccount.
FieldRequiredTypeDescription
contactIdRequiredstringThe contact to stop the AI for. You get this from the create contact response.

Request

curl -X POST https://d3vzg2unix0bsb.cloudfront.net/webhook/ai/stop \
  -H "Content-Type: application/json" \
  -H "x-api-key: sk_your_key_here" \
  -d '{"contactId":"ctc_abc123"}'

Response

{
  "status": "success",
  "message": "AI stopped for contact",
  "data": {
    "contactId": "ctc_abc123",
    "isPaused": true,
    "status": "disabled",
    "aiSync": "synced"
  }
}
POST/webhook/ai/start

Turn the AI back on for a contact you previously stopped

Re-enables the AI for a single contact after a stop. The AI resumes replying and running follow up sequences. It does not send any message by itself. To have the AI reach out first, use Start an AI conversation.

No message is sent when you call this. The AI simply resumes handling the conversation.
If an automation had switched the contact to follow ups off with replies on, starting preserves that setting instead of overriding it. The status comes back as followups-disabled.
Returns 404 if the contactId does not exist in your subaccount.
FieldRequiredTypeDescription
contactIdRequiredstringThe contact to re-enable the AI for. You get this from the create contact response.

Request

curl -X POST https://d3vzg2unix0bsb.cloudfront.net/webhook/ai/start \
  -H "Content-Type: application/json" \
  -H "x-api-key: sk_your_key_here" \
  -d '{"contactId":"ctc_abc123"}'

Response

{
  "status": "success",
  "message": "AI started for contact",
  "data": {
    "contactId": "ctc_abc123",
    "isPaused": false,
    "status": "enabled",
    "aiSync": "synced"
  }
}
POST/webhooks/messages/inbound

Your CRM delivers a message directly to a contact via Sendify

Use this when a CRM user wants to send a message to a contact through Sendify. The message is delivered over the channel you specify. No AI is triggered, this is a direct send on behalf of your system.

The contact must already exist in Sendify. Create them first with the create contact endpoint.
Always pass externalMessageId if your system might retry failed requests. It prevents the same message from being delivered twice.
FieldRequiredTypeDescription
phoneRequiredstringThe contact's phone number. Sendify uses this to find their contact record.
messageOptionalstringThe text of the message to send. Either message or attachments must be present.
attachmentsOptionalstring[]URLs of any media attachments, images or files, to include in the message.
contactIdOptionalstringIf you already know the Sendify contactId, provide it to speed up the lookup.
channelOptionalstringThe messaging channel: SMS, IMESSAGE, MMS, WHATSAPP, or RCS. Defaults to SMS.
externalMessageIdOptionalstringA unique ID from your system. If you send the same ID twice, the second call is ignored, which prevents duplicate messages on retries.

Request

curl -X POST https://d3vzg2unix0bsb.cloudfront.net/webhooks/messages/inbound \
  -H "Content-Type: application/json" \
  -H "x-api-key: sk_your_key_here" \
  -d '{"phone":"+15551234567","message":"Hi Jane, just following up!","contactId":"ctc_abc123","externalMessageId":"msg_001","channel":"IMESSAGE"}'

Response

{
  "status": "success",
  "message": "Message accepted",
  "data": {
    "messageId": "5917",
    "contactId": "ctc_abc123"
  }
}

Outbound webhook events

These are not endpoints you call. Sendify posts them to the outbound webhook URL configured on your subaccount so your CRM stays in sync. Every payload carries a type field naming the event. Message events are flat. Set your webhook to POST. The other events wrap their details in a data object alongside a common envelope of type, subaccountId, contactId, and occurredAt.

Outbound events run the other direction

You do not call these. Sendify posts them to the webhook URL on your subaccount, and your endpoint answers with any 2xx within 10 seconds.

Something happens

A reply lands, the AI sends a message, a contact changes, or a booking is created.

Sendify posts to you

One JSON payload with a type field naming the event.

Your CRM updates

Answer 2xx in 10 seconds. Failures retry with backoff for about two days.

message.inbound / message.outbound

Flat payload. Fields sit at the top level.

contact.updated

Wrapped in data with an old to new diff.

appointment.booked

Wrapped in data with the booking details.

Delivery is durable. Respond with any 2xx status within 10 seconds. If your endpoint times out or errors, Sendify retries with increasing backoff for up to about two days, and deliveries survive restarts.
Validate the custom headers you configured on the subaccount. They act as a shared secret proving the request came from Sendify.
POSTmessage.inbound / message.outbound

Contact replies and AI-sent messages

Sent for the two message events: when a contact sends a reply, type message.inbound with direction inbound, and when the AI sends a message to a contact, type message.outbound with direction outbound. Use the type or direction field to label each message in your conversation log.

FieldRequiredTypeDescription
typeOptionalstringThe event type: message.inbound for a contact reply, message.outbound for an AI message.
eventOptionalstringAlias of type, same value.
subaccountIdOptionalstringThe Sendify subaccount the message belongs to.
contactIdOptionalstringThe Sendify contact ID.
contactOptionalobject | nullFull contact record for the recipient or sender. Null if the contact cannot be resolved.
contact.idOptionalstringThe contact ID, mirrors contactId.
contact.firstNameOptionalstring | nullContact's first name.
contact.lastNameOptionalstring | nullContact's last name.
contact.emailOptionalstring | nullContact's email address.
contact.phoneOptionalstring | nullContact's phone number.
contact.tagsOptionalstring[]Tags attached to the contact, used for AI routing.
contact.timezoneOptionalstring | nullIANA timezone for the contact, if known.
contact.customFieldsOptionalobjectCustom field values keyed by field key. Empty object if none are set.
phoneOptionalstringThe contact's phone number on this message.
messageOptionalstringThe message text.
attachmentsOptionalstring[] | nullAny media files included in the message, or null.
channelOptionalstringThe messaging channel, for example SMS or IMESSAGE.
directionOptionalstringinbound when the contact sent it, outbound when the AI sent it.
messageIdOptionalstringSendify's internal ID for this message.
timestampOptionalstringISO 8601 timestamp of when the message was sent.

Your endpoint should respond with 2xx

{
  "ok": true
}
POSTcontact.updated

A contact's fields, custom fields, or tags changed

Fires whenever a contact changes: a manual edit, an AI automation writing a field, a tag added or removed, or a lead routing rule. One bundled event per change, carrying a structured diff of old to new so you can apply exactly what changed without re-fetching the contact.

Only the keys that actually changed appear under standardFields and customFields. A tags only change leaves both empty and populates tagsAdded and tagsRemoved. data.contact always holds the full current state.
FieldRequiredTypeDescription
typeOptionalstringAlways contact.updated.
eventOptionalstringAlias of type.
agencyIdOptionalstringThe Sendify agency.
subaccountIdOptionalstringThe Sendify subaccount.
contactIdOptionalstringThe contact that changed.
occurredAtOptionalstringISO 8601 timestamp of the change.
data.changedOptionalstring[]Which areas changed: standard field names plus customFields and tags.
data.standardFieldsOptionalobjectChanged standard fields keyed by name (firstName, lastName, email, phone, timezone), each with old and new.
data.customFieldsOptionalobjectChanged custom fields keyed by field key, each with old and new.
data.tagsAddedOptionalstring[]Tags added in this change, empty if none.
data.tagsRemovedOptionalstring[]Tags removed in this change, empty if none.
data.contactOptionalobjectFull current snapshot of the contact: firstName, lastName, email, phone, tags, timezone, customFields.

Your endpoint should respond with 2xx

{
  "ok": true
}
POSTappointment.booked

A booking was created for a contact

Fires when an appointment is booked for a contact, whether by the AI, an automation, or directly.

FieldRequiredTypeDescription
typeOptionalstringAlways appointment.booked.
agencyIdOptionalstringThe Sendify agency.
subaccountIdOptionalstringThe Sendify subaccount.
contactIdOptionalstringThe contact the appointment is for.
occurredAtOptionalstringISO 8601 timestamp of the booking.
data.appointmentIdOptionalstringThe booking's ID in the calendar provider.
data.titleOptionalstring | nullAppointment title.
data.startTimeOptionalstringISO 8601 start time.
data.endTimeOptionalstringISO 8601 end time.
data.timezoneOptionalstring | nullTimezone of the appointment.
data.providerOptionalstringCalendar provider: google or ghl.
data.calendarIdOptionalstringThe calendar the appointment was booked on.

Your endpoint should respond with 2xx

{
  "ok": true
}
POSTagent_status.changed

The AI was turned on, off, or set to no follow-ups for a contact

Fires when a contact's AI agent status changes, from the dashboard toggle, an automation, or when the AI reaches its goal and stops its own follow-ups.

FieldRequiredTypeDescription
typeOptionalstringAlways agent_status.changed.
agencyIdOptionalstringThe Sendify agency.
subaccountIdOptionalstringThe Sendify subaccount.
contactIdOptionalstringThe contact whose status changed.
occurredAtOptionalstringISO 8601 timestamp of the change.
data.statusOptionalstringThe new status: active (AI on), disabled (AI off), or followups-disabled (replies on, no proactive follow-ups).

Your endpoint should respond with 2xx

{
  "ok": true
}
POSTautomation.completed

An AI automation or workflow finished running for a contact

Fires once when an automation workflow finishes for a contact, so your CRM can track that the automation ran.

Fires once per run. De-duplicate on data.executionId. status is completed when the run finished and failed if a step errored.
FieldRequiredTypeDescription
typeOptionalstringAlways automation.completed.
subaccountIdOptionalstringThe Sendify subaccount.
contactIdOptionalstringThe contact the automation ran for.
occurredAtOptionalstringISO 8601 timestamp of completion.
data.executionIdOptionalstringUnique ID for this workflow run. Use it to de-duplicate.
data.workflowIdOptionalstringThe workflow that ran.
data.statusOptionalstringcompleted if it finished, failed if a step errored.
data.triggerOptionalstringWhat triggered the run.

Your endpoint should respond with 2xx

{
  "ok": true
}
POSTsummary.generated

A Generate Summary automation produced a conversation summary

Fires when a Generate Summary automation node produces a summary of the conversation. The summary is also written to a contact field.

FieldRequiredTypeDescription
typeOptionalstringAlways summary.generated.
subaccountIdOptionalstringThe Sendify subaccount.
contactIdOptionalstringThe contact the summary is for.
occurredAtOptionalstringISO 8601 timestamp.
data.summaryOptionalstringThe generated summary text.
data.targetFieldOptionalstringThe contact field the summary was written to.

Your endpoint should respond with 2xx

{
  "ok": true
}

Setting this up in the portal

Everything above is switched on from Subaccounts in the Sendify portal. Two dialogs cover it: the key dialog and the custom location configuration.

Webhook API key

Subaccounts, then the key icon on the row. The key starts with sk_ and is shown once. Pass it as the x-api-key header on every call. Regenerate or revoke from the same dialog, and remember that regenerating breaks any integration still using the old key.

Custom location configuration

Subaccounts, then the gear icon on a custom location. Set the outbound webhook URL Sendify posts AI replies and events to, the timezone used for message times and business hours, and up to three custom headers sent on every outbound webhook so your receiver can verify the request.

Custom location configurationIllustrative mock, portal UI may differ
Location nameMy Custom Location
Outbound webhook URLhttps://your-crm.com/hook
TimezoneAmerica/New_York
Custom headersAuthorization: Bearer … (up to 3)
Save locationCancel

Create the location once, then reopen it from the gear icon any time to change the webhook URL, timezone, or headers.

Create it once, edit it any time

  1. 1Create the custom location from Subaccounts. Fill in the name, address, email, phone, outbound webhook URL, timezone, and optional custom headers.
  2. 2Reopen it later from the gear icon on the location row to update the webhook URL, timezone, or headers.
  3. 3Changes apply to every outbound webhook Sendify posts for that subaccount.
FieldRequiredTypeDescription
Location NameRequiredtextLabel for the custom location inside Sendify.
AddressRequiredtextBusiness address on the location.
EmailRequiredtextContact email on the location.
Phone NumberRequiredtextE.164 format, for example 15672011207.
Outbound Webhook URLRequiredurlWhere Sendify POSTs AI replies and every outbound event.
TimezoneRequiredselectUsed for displaying message times and business-hours scheduling.
Custom HeadersOptionalup to 3For example Authorization: Bearer ... Sent on every outbound webhook.
A custom location bypasses LeadConnector and integrates with your own messaging system: Sendify POSTs AI replies to your outbound URL, and your system pushes inbound replies to /webhooks/messages/inbound.

FAQ

Where do I find my API key?

In the Sendify portal under Agency View, open API docs and pick the subaccount. The key starts with sk_ and belongs to that one subaccount, so it decides where your contacts and conversations land.

Do I need to send the key on every request?

Yes. Every request needs the header x-api-key with your sk_ key. Without it the request is rejected.

Why will the AI not start a conversation?

Two reasons cover almost every case. The contact has no tag that matches an agent routing rule, or you never pushed the contact first so you do not have a valid contactId.

Does stopping the AI send anything to the lead?

No. Stop and start only flip the AI on or off for that contact. Only Start an AI conversation sends a message.

How do I send a message without involving the AI?

Use the inbound messages endpoint. It delivers your text straight to the contact on the channel you pick and never triggers the AI. Pass externalMessageId so a retry cannot send the same message twice.

Do I have to call the outbound webhook events?

No. Sendify posts those to the webhook URL on your subaccount. Your endpoint just answers with any 2xx status within 10 seconds. Failed deliveries retry with backoff for about two days.

Where do I set the outbound webhook URL and custom headers?

In the portal under Subaccounts, open the gear icon on a custom location. You set the outbound webhook URL, the timezone used for message times and business hours, and up to three custom headers that are sent on every outbound webhook so your receiver can verify the request.

Can I call stop twice?

Yes. Stopping a contact that is already stopped returns the same result, so it is safe to retry.

Related articles

Browse all help articles

Still need a hand?

If your volume is going to run past the daily or hourly limits, or something is not behaving the way these docs describe, talk to our team.