Slingvo Zapier Integration & Public API

Developer documentation · Slingvo Public API v1

Slingvo is a sales dialer and CRM built for real estate agents. The Slingvo Zapier integration lets an account send contact cards and call dispositions from Slingvo to the CRM you already use, and lets other tools create or update contacts in Slingvo.

  • Base URL: https://api.slingvo.com/api/public/v1
  • Authentication: Authorization: Bearer slk_...
  • Rate limit: 60 requests per minute per API key
  • Support: helpme@slingvo.com

Contents

  1. Status of this document
  2. What the integration does
  3. Getting started: create an API key and connect Zapier
  4. Authentication and base URL
  5. Scopes
  6. Conventions, rate limits and errors
  7. Endpoint reference
  8. Webhooks
  9. Verifying webhook signatures
  10. Zapier triggers, actions and fields
  11. Data fields sent to other CRMs
  12. Limitations
  13. Support and legal

1. Status of this document

This page separates what has been tested from what is described by the API contract only. Nothing in the second group should be read as a tested guarantee.

Item Status
Request and response shapes for every endpoint in section 7 Verified against Slingvo's staging API (read-only calls, plus creating and deleting test records).
Error codes, the 60/minute limit header, pagination, validation messages Verified on staging.
contact.sent_to_crm accepted as a subscribable event Verified on staging.
Webhook payload shapes (section 8) and the signing scheme (section 9) Per the Slingvo API contract. The payloads in this page are illustrative examples built from that contract with fake data. They are not captures of live deliveries.
Zapier signature verification against a real Zapier-delivered webhook Not yet exercised.
Production hostname api.slingvo.com Live. DNS resolves, TLS is valid, and the host is served over https by the production backend (checked 2026-09-30 CT).
Public API v1 on production (temporary note: delete this row once production goes live) As of 2026-09-30 the public API v1 endpoints are deployed on staging only. GET https://api.slingvo.com/api/public/v1/me currently returns 404 until the public API reaches production, so the examples on this page were tested against a staging host with the same paths.
Slingvo app listing in the Zapier directory Not published yet. The integration is private while it is under review.
In-app screen for creating API keys Not confirmed. See section 3.

All example data is fictional: phone numbers use the 555-01xx range and email addresses use example.com. Identifiers are placeholders.


2. What the integration does

The Slingvo Zapier integration connects your Slingvo account to the thousands of apps on Zapier. The main use is sending Slingvo activity into a CRM:

  • When an agent logs a disposition (for example "Appointment Set") on a call, a Zap can push that contact, with the disposition, to your CRM.
  • When a contact is created or updated in Slingvo, a Zap can create or update the matching record in your CRM.
  • When an agent clicks Send to CRM on a contact card, a Zap can receive the full contact card. (This trigger is hidden in Zapier until it has been verified end to end.)
  • In the other direction, a Zap can create, update or find Slingvo contacts from lead sources such as forms and ad platforms.

It is designed to work with any CRM that has a Zapier app, for example Lofty, Follow Up Boss, Wise Agent and BoldTrail. Those are examples of CRMs available on Zapier; this page does not claim an official partnership with any of them.

Under the hood the integration uses the Slingvo Public API documented below: a REST API over HTTPS with JSON bodies, API-key authentication and signed webhooks.


3. Getting started: create an API key and connect Zapier

Who can create a key

Only a Slingvo account admin can create API keys. Agent accounts cannot. An account can hold up to 25 active keys.

Create a key

Keys are created with the account-level endpoint POST /api/api-keys (note: no /public/v1), called as a logged-in admin session. The key is not accepted for this route: requests that use an slk_ key to call /api/api-keys return 401.

Request body:

{
  "name": "Zapier",
  "scopes": ["contacts:read", "contacts:write", "webhooks:manage"]
}

The response contains the key in data.key. It looks like slk_.... It is shown once. Slingvo stores only a SHA-256 hash, so a lost key cannot be recovered; create a new one and revoke the old one.

In-app screen not confirmed. When this page was written, an in-app settings page for creating API keys could not be confirmed in the Slingvo web app. If you are an admin and do not see a way to create a key, email helpme@slingvo.com and we will help you create one.

List and revoke keys (admin session, same base path):

Action Request
List keys GET /api/api-keys
Revoke a key DELETE /api/api-keys/:id

Revoking a key also revokes every webhook subscription that key created.

Connect in Zapier

The Slingvo integration is currently private (not yet listed in the public Zapier app directory), so you connect through an invitation link from Slingvo. Once you have access:

  1. In Zapier, create a Zap (or edit one) and choose Slingvo as the app for a trigger or action.
  2. When asked to connect an account, choose Sign in / Add a new account.
  3. Paste your API key (slk_...) into the Slingvo API Key field.
  4. Zapier tests the connection by calling GET /me. If it succeeds, the account is connected and labelled with your Slingvo account name.
  5. Pick a trigger or action and continue building the Zap.

The key you use in Zapier needs the three scopes contacts:read, contacts:write and webhooks:manage (see section 5).

Example: Slingvo disposition → your CRM

  1. Trigger: Slingvo – Disposition Logged.
  2. Action: your CRM's "Create/Update Contact" (or "Add Lead") action.
  3. Map Slingvo fields (for example fullName, email, phone, disposition) to the CRM's fields. The mapping is your choice and depends on the CRM; see section 11 for what Slingvo provides.

4. Authentication and base URL

Base URL

https://api.slingvo.com/api/public/v1

Every request needs an API key in the Authorization header:

Authorization: Bearer slk_xxxxxxxxxxxxxxxxxxxxxxxx

Requests with a body use JSON:

Content-Type: application/json
Accept: application/json

Every successful response has this shape:

{ "success": true, "data": { } }

A key identifies one Slingvo account (tenant). Everything you read or write through the key belongs to that account.

Quick check that a key works:

curl -s https://api.slingvo.com/api/public/v1/me \
  -H "Authorization: Bearer $SLINGVO_API_KEY"

5. Scopes

Scopes are chosen when the key is created. They do not imply each other.

Scope Allows
contacts:read Find and list contacts; list dispositions, custom fields, lists and folders
contacts:write Create and update contacts
webhooks:manage Subscribe, list, rotate and unsubscribe webhooks
(none) GET /me needs a valid key but no particular scope

A request made without the needed scope returns 403 insufficient_scope.


6. Conventions, rate limits and errors

Rate limit

  • 60 requests per minute per API key.
  • Every response includes X-RateLimit-Limit (60) and X-RateLimit-Remaining.
  • When the limit is exceeded you get 429 rate_limited with a Retry-After header (seconds). Wait that long before retrying.
  • Requests with a missing or invalid key share a stricter per-IP budget of 30 per minute.

Error envelope

All errors from the API use one shape:

{
  "success": false,
  "error": {
    "code": "validation_error",
    "message": "An email or phone is required"
  }
}

Error codes

HTTP status error.code Meaning
400 validation_error The request is malformed or a value is invalid (for example Invalid limit, Invalid cursor, Webhook URL must be https, Unknown event).
401 unauthorized The API key is missing, malformed, revoked or unknown.
403 insufficient_scope The key is valid but lacks the scope this endpoint needs.
403 forbidden The action is not allowed for this key.
404 not_found The contact or webhook subscription does not exist in this account.
409 conflict The request conflicts with existing data: an update would attach an email or phone that belongs to another contact; an Idempotency-Key was reused with a different body; the webhook URL is already subscribed; or the account has reached the subscription limit (50).
409 ambiguous_match GET /contacts/find was given an email and a phone that match two different contacts.
429 rate_limited Rate limit exceeded. See Retry-After.
500 internal_error Server error. The message is always Internal server error; retry later or contact support.

A request to a path that does not exist on the API host returns a plain HTML "Cannot GET ..." 404 page rather than the JSON envelope. Check the path if you see one.

Pagination

List endpoints that paginate use an opaque cursor: pass limit (1–100, default 25) and cursor. The response includes nextCursor, which is null on the last page.

Data formats

  • Phone numbers are stored and returned in E.164 (+15555550142). You can send common US formats; they are normalized.
  • Email addresses are lowercased.
  • Timestamps are ISO-8601 UTC.

7. Endpoint reference

All paths below are relative to https://api.slingvo.com/api/public/v1. In the examples, $KEY is your API key.

The contact object

Returned by the contact endpoints and inside contact.created / contact.updated events.

{
  "id": "ckm8x2q1a0001abcd1234wxyz",
  "fullName": "Maria Alvarez",
  "address": "4821 Maple Grove Ln",
  "address2": "",
  "city": "Austin",
  "state": "TX",
  "zip": "78745",
  "mailingAddress": "",
  "mailingAddress2": "",
  "mailingCity": "",
  "mailingState": "",
  "mailingZip": "",
  "source": "facebook",
  "tags": ["expired-listing", "zapier"],
  "notes": ["Asked about relisting in the spring."],
  "description": "",
  "status": "PENDING",
  "disposition": "Appointment Set",
  "customFields": { "MLS ID": "4471290", "Timeline": "0-3 months" },
  "emails": [
    { "id": "cke1abcd", "email": "maria.alvarez@example.com", "isPrimary": true }
  ],
  "phones": [
    { "id": "ckp1abcd", "number": "+15125550142", "type": "MOBILE", "isBestNumber": true, "isDnc": false }
  ],
  "createdAt": "2026-09-28T14:03:11.000Z",
  "updatedAt": "2026-09-29T16:45:02.000Z"
}
Field Notes
phones[].type One of MOBILE, TELEPHONE, HOME, WORK.
phones[].isDnc true if the number is flagged Do Not Call in Slingvo.
customFields Keys are the custom field names returned by GET /custom-fields.
disposition, status Read-only through the API (see section 12).

GET /me

Returns information about the API key and account. Use it to test a connection.

Scope: none (valid key only)

curl -s https://api.slingvo.com/api/public/v1/me -H "Authorization: Bearer $KEY"
{
  "success": true,
  "data": {
    "keyId": "ckk0example0001",
    "name": "Zapier",
    "prefix": "slk_abcd",
    "scopes": ["contacts:read", "contacts:write", "webhooks:manage"],
    "tenant": {
      "id": "ckt0example0001",
      "name": "Example Realty Team",
      "email": "admin@example.com"
    }
  }
}

GET /contacts

Lists contacts, newest first.

Scope: contacts:read

Query parameter Description
limit 1–100, default 25. Other values return 400 validation_error ("Invalid limit").
cursor The nextCursor from the previous page. A bad cursor returns 400 ("Invalid cursor").
curl -s "https://api.slingvo.com/api/public/v1/contacts?limit=2" \
  -H "Authorization: Bearer $KEY"
{
  "success": true,
  "data": {
    "contacts": [
      { "id": "ckm8x2q1a0001abcd1234wxyz", "fullName": "Maria Alvarez", "...": "full contact object" },
      { "id": "ckm8x2q1a0002abcd1234wxyz", "fullName": "Sam Rivera", "...": "full contact object" }
    ],
    "nextCursor": "eyJpZCI6IjEyMyJ9"
  }
}

nextCursor is null on the last page.


GET /contacts/find

Finds one contact by email and/or phone.

Scope: contacts:read

Query parameter Description
email Email address (case-insensitive).
phone Phone number in any common format; matched against the stored E.164 number.

At least one is required, otherwise 400 ("email or phone is required"). An unparseable phone returns 400 ("Phone could not be normalized"). If email and phone match two different contacts, the response is 409 ambiguous_match.

curl -s "https://api.slingvo.com/api/public/v1/contacts/find?email=maria.alvarez@example.com" \
  -H "Authorization: Bearer $KEY"
{
  "success": true,
  "data": {
    "contact": { "id": "ckm8x2q1a0001abcd1234wxyz", "fullName": "Maria Alvarez", "...": "full contact object" },
    "matchCount": 1
  }
}

No match:

{ "success": true, "data": { "contact": null, "matchCount": 0 } }

If several contacts match, the most recently updated one is returned and matchCount tells you how many matched.


POST /contacts

Creates a contact, or returns the existing one if the email or phone already matches.

Scope: contacts:write

Body field Type Notes
fullName string Or use firstName and/or lastName. A name is required.
email / emails string / array At least one email or phone is required.
phone / phones string / array Normalized to E.164.
address, address2, city, state, zip string Property address.
mailingAddress, mailingAddress2, mailingCity, mailingState, mailingZip string Mailing address.
tags array of strings
notes string or array
source string Lead source. Defaults to public-api.
customFields object Keys are field names from GET /custom-fields.
listId / folderId string Place the contact in a list or folder (IDs from GET /lists, GET /folders). If you omit listId, the contact is owned by the account admin and agents on the team will not see it.

Optional header Idempotency-Key (1–200 characters: letters, digits, _ - : .). Reusing a key with a different body returns 409 conflict.

curl -s -X POST https://api.slingvo.com/api/public/v1/contacts \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: lead-2026-000123" \
  -d '{
    "fullName": "Jordan Lee",
    "email": "jordan.lee@example.com",
    "phone": "(512) 555-0143",
    "city": "Austin",
    "state": "TX",
    "source": "website-form",
    "tags": ["zapier"],
    "notes": "Requested a home valuation.",
    "listId": "ckl0example0001"
  }'

201 Created (new contact):

{
  "success": true,
  "data": {
    "contact": {
      "id": "ckm8x2q1a0003abcd1234wxyz",
      "fullName": "Jordan Lee",
      "emails": [{ "id": "cke2abcd", "email": "jordan.lee@example.com", "isPrimary": true }],
      "phones": [{ "id": "ckp2abcd", "number": "+15125550143", "type": "MOBILE", "isBestNumber": true, "isDnc": false }],
      "source": "website-form",
      "tags": ["zapier"],
      "notes": ["Requested a home valuation."],
      "...": "remaining contact fields"
    },
    "deduped": false
  }
}

200 OK with "deduped": true means a contact with that email or phone already existed. It is returned unchanged, nothing was overwritten, and no contact.created event is emitted.

Missing contact details:

{ "success": false, "error": { "code": "validation_error", "message": "An email or phone is required" } }

PATCH /contacts/:id

Partially updates a contact. Send only the fields you want to change.

Scope: contacts:write

Merge rules:

  • Phones and emails are merged in; existing ones are never deleted.
  • Blank values are ignored (they do not erase data).
  • Do-not-call and invalid-number flags are never cleared.
  • Notes are appended.
  • Tags, when present, replace the existing tags. Send "tags": [] to clear them.
  • customFields are merged; set a key to null to remove it.
  • Attaching an email or phone that belongs to a different contact returns 409 conflict.
curl -s -X PATCH https://api.slingvo.com/api/public/v1/contacts/ckm8x2q1a0003abcd1234wxyz \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "512-555-0144",
    "notes": "Prefers text messages.",
    "tags": ["zapier", "valuation"],
    "customFields": { "Timeline": "3-6 months" }
  }'
{
  "success": true,
  "data": {
    "contact": {
      "id": "ckm8x2q1a0003abcd1234wxyz",
      "fullName": "Jordan Lee",
      "phones": [
        { "id": "ckp2abcd", "number": "+15125550143", "type": "MOBILE", "isBestNumber": true, "isDnc": false },
        { "id": "ckp3abcd", "number": "+15125550144", "type": "MOBILE", "isBestNumber": false, "isDnc": false }
      ],
      "tags": ["zapier", "valuation"],
      "notes": ["Requested a home valuation.", "Prefers text messages."],
      "customFields": { "Timeline": "3-6 months" },
      "...": "remaining contact fields"
    }
  }
}

An unknown id returns:

{ "success": false, "error": { "code": "not_found", "message": "Contact not found" } }

GET /dispositions

Lists the account's dispositions. Dispositions are configured per account, so your list will differ from the example.

Scope: contacts:read

{
  "success": true,
  "data": {
    "dispositions": [
      { "id": "ckd0example0001", "label": "Appointment Set", "value": "APPOINTMENT_SET", "color": "#22c55e", "isSystem": false, "isActive": true, "order": 3, "ownerUserId": null },
      { "id": "ckd0example0002", "label": "No Answer", "value": "NO_ANSWER", "color": "#9ca3af", "isSystem": false, "isActive": true, "order": 2, "ownerUserId": null }
    ]
  }
}

GET /custom-fields

Lists the account's custom contact fields. Use the name as the key in customFields when creating or updating contacts.

Scope: contacts:read

{
  "success": true,
  "data": {
    "customFields": [
      { "id": "ckf0example0001", "name": "MLS ID", "type": "text", "options": [], "sortOrder": 1, "ownerUserId": null },
      { "id": "ckf0example0002", "name": "Timeline", "type": "text", "options": [], "sortOrder": 2, "ownerUserId": null }
    ]
  }
}

type and options describe the field. All custom fields observed on staging were text with no options; do not assume other types behave differently until you have tested them.


GET /lists

Lists the account's contact lists. Use an id as listId in POST /contacts.

Scope: contacts:read

{
  "success": true,
  "data": { "lists": [ { "id": "ckl0example0001", "name": "Zapier Leads" } ] }
}

GET /folders

Lists the account's folders. Use an id as folderId in POST /contacts.

Scope: contacts:read

{
  "success": true,
  "data": { "folders": [ { "id": "ckg0example0001", "name": "Expired Listings" } ] }
}

POST /hooks/subscribe

Registers a webhook URL to receive events. The Zapier integration calls this automatically when a Zap with a Slingvo trigger is turned on.

Scope: webhooks:manage

Body field Type Notes
url string Must be https. http returns 400 ("Webhook URL must be https"). Private and loopback addresses are not allowed, and redirects are not followed (per the API contract).
events array of strings One or more of contact.created, contact.updated, disposition.logged, contact.sent_to_crm. An unknown name returns 400 ("Unknown event").
curl -s -X POST https://api.slingvo.com/api/public/v1/hooks/subscribe \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://hooks.example.com/slingvo", "events": ["disposition.logged"] }'

201 Created:

{
  "success": true,
  "data": {
    "id": "cks0example0001",
    "url": "https://hooks.example.com/slingvo",
    "events": ["disposition.logged"],
    "secret": "whsec_EXAMPLE_ONLY_NOT_A_REAL_SECRET",
    "createdAt": "2026-09-29T15:00:00.000Z"
  }
}

Save secret now. It is returned only in this response and is used to verify webhook signatures (section 9). If you lose it, call POST /hooks/:id/rotate.

Limits: an account can have up to 50 live subscriptions, and subscribing the same active URL twice returns 409 conflict.


GET /hooks

Lists your webhook subscriptions. The secret is never included.

Scope: webhooks:manage

{
  "success": true,
  "data": {
    "subscriptions": [
      {
        "id": "cks0example0001",
        "url": "https://hooks.example.com/slingvo",
        "events": ["disposition.logged"],
        "createdAt": "2026-09-29T15:00:00.000Z",
        "disabledAt": null
      }
    ]
  }
}

A non-null disabledAt means Slingvo stopped delivering to that subscription after repeated failures (see "Delivery and retries").


DELETE /hooks/:id

Removes a subscription. The Zapier integration calls this automatically when a Zap is turned off.

Scope: webhooks:manage

curl -s -X DELETE https://api.slingvo.com/api/public/v1/hooks/cks0example0001 \
  -H "Authorization: Bearer $KEY"
{ "success": true, "data": { "id": "cks0example0001", "revoked": true } }

An unknown id returns 404 not_found ("Webhook subscription not found").


POST /hooks/:id/rotate

(Not required by the Zapier integration.) Generates a new secret for the subscription, re-enables it if it was disabled, and returns the new secret once.

Scope: webhooks:manage


8. Webhooks

Instead of polling, subscribe to events and Slingvo will POST a JSON body to your URL when they happen.

Events

Event Fires when
contact.created A contact is created in Slingvo, in the app or through the API. A POST /contacts call that matches an existing contact does not fire it.
contact.updated A contact is updated, in the app or through the API.
disposition.logged An agent logs a disposition on a contact (for example after a call).
contact.sent_to_crm An agent clicks Send to CRM on a contact in the Slingvo app.

Event envelope

Every delivery uses this envelope:

{
  "id": "evt_example_0001",
  "type": "contact.created",
  "createdAt": "2026-09-29T15:30:00.000Z",
  "contactId": "ckm8x2q1a0001abcd1234wxyz",
  "dispositionLogId": null,
  "origin": "app",
  "apiKeyId": null,
  "data": { }
}
Field Notes
id Unique event id. The same value is sent as the X-Slingvo-Delivery header. Use it to de-duplicate, because a delivery can be retried.
type The event name.
origin app (a person using Slingvo) or public_api (a call through this API).
apiKeyId For public_api events, the id of the API key that made the change. null otherwise. If your Zap both listens to events and writes contacts, ignore events whose apiKeyId is your own key to avoid loops.
data Event-specific; see below.

Delivery headers

Header Value
Content-Type application/json
User-Agent Slingvo-Webhooks/1
X-Slingvo-Event The event name
X-Slingvo-Delivery The event id (same as id)
X-Slingvo-Signature t=<unix seconds>,v1=<hex HMAC> (see section 9)

contact.created and contact.updated

data.contact is the contact object.

{
  "id": "evt_example_0001",
  "type": "contact.created",
  "createdAt": "2026-09-29T15:30:00.000Z",
  "contactId": "ckm8x2q1a0001abcd1234wxyz",
  "dispositionLogId": null,
  "origin": "public_api",
  "apiKeyId": "ckk0example0001",
  "data": {
    "contact": {
      "id": "ckm8x2q1a0001abcd1234wxyz",
      "fullName": "Maria Alvarez",
      "city": "Austin",
      "state": "TX",
      "source": "facebook",
      "tags": ["expired-listing"],
      "notes": [],
      "customFields": {},
      "emails": [{ "id": "cke1abcd", "email": "maria.alvarez@example.com", "isPrimary": true }],
      "phones": [{ "id": "ckp1abcd", "number": "+15125550142", "type": "MOBILE", "isBestNumber": true, "isDnc": false }],
      "createdAt": "2026-09-29T15:30:00.000Z",
      "updatedAt": "2026-09-29T15:30:00.000Z",
      "...": "remaining contact fields as in the contact object"
    }
  }
}

disposition.logged

{
  "id": "evt_example_0002",
  "type": "disposition.logged",
  "createdAt": "2026-09-29T16:45:02.000Z",
  "contactId": "ckm8x2q1a0001abcd1234wxyz",
  "dispositionLogId": "ckdl0example0001",
  "origin": "app",
  "apiKeyId": null,
  "data": {
    "dispositionLogId": "ckdl0example0001",
    "contactId": "ckm8x2q1a0001abcd1234wxyz",
    "disposition": { "id": "ckd0example0001", "label": "Appointment Set", "value": "APPOINTMENT_SET" },
    "appliedById": "cku0example0001",
    "folderId": null,
    "source": "CALL",
    "contact": {
      "id": "ckm8x2q1a0001abcd1234wxyz",
      "fullName": "Maria Alvarez",
      "emails": [{ "id": "cke1abcd", "email": "maria.alvarez@example.com", "isPrimary": true }],
      "phones": [{ "id": "ckp1abcd", "number": "+15125550142", "type": "MOBILE", "isBestNumber": true, "isDnc": false }],
      "customFields": { "MLS ID": "4471290" },
      "...": "remaining contact fields"
    }
  }
}
Field Notes
data.source CALL (logged after a call) or MANUAL (logged by hand).
data.contact The contact card at the time of the event, in the same camelCase shape as the contact object. It is null if the contact no longer exists.
data.folderId The folder the contact was in when the disposition was applied, if any.

An automatic "Contacted" outcome that Slingvo refuses to apply does not emit this event.

contact.sent_to_crm

Fires when an agent clicks Send to CRM in the Slingvo app. It is not fired by ordinary contact or disposition changes. apiKeyId is null and origin is app.

Unlike the other events, data here uses snake_case names, with a complete contact card:

{
  "id": "ckm8x2q1a0001abcd1234wxyz:2026-09-29T18:04:00.000Z",
  "type": "contact.sent_to_crm",
  "createdAt": "2026-09-29T18:04:00.000Z",
  "contactId": "ckm8x2q1a0001abcd1234wxyz",
  "dispositionLogId": null,
  "origin": "app",
  "apiKeyId": null,
  "data": {
    "event": "contact.sent_to_crm",
    "event_id": "ckm8x2q1a0001abcd1234wxyz:2026-09-29T18:04:00.000Z",
    "sent_at": "2026-09-29T18:04:00.000Z",
    "source": "contact_card",
    "disposition": "Appointment Set",
    "contact": {
      "id": "ckm8x2q1a0001abcd1234wxyz",
      "full_name": "Maria Alvarez",
      "first_name": "Maria",
      "last_name": "Alvarez",
      "emails": [{ "email": "maria.alvarez@example.com", "is_primary": true }],
      "phones": [{ "number": "+15125550142", "type": "MOBILE", "is_best_number": true }],
      "address": { "street": "4821 Maple Grove Ln", "city": "Austin", "state": "TX", "zip": "78745" },
      "mailing_address": { "street": null, "city": null, "state": null, "zip": null },
      "tags": ["expired-listing"],
      "source": "facebook",
      "notes": ["Asked about relisting in the spring."],
      "custom_fields": { "MLS ID": "4471290", "Timeline": "0-3 months" },
      "created_at": "2026-09-28T14:03:11.000Z",
      "updated_at": "2026-09-29T16:45:02.000Z"
    }
  }
}
Field Notes
data.source Where the button was clicked: dialer or contact_card. This is not the lead source; the lead source is data.contact.source.
data.disposition The contact's current disposition label, or null.
data.sent_at UTC timestamp with milliseconds. data.event_id is the contact id plus this timestamp, so clicking the button again creates a new event.
contact.address.street Address line 1 and line 2 joined with , .
contact.first_name / last_name Derived by splitting full_name at the first space (Slingvo stores a single full name).
contact.custom_fields Keyed by the custom field label.

The payload deliberately leaves out: internal owner ids, secrets, dialer status, call records, and the per-number do-not-call flag.

Delivery and retries

  • Slingvo considers a delivery successful when your endpoint returns any 2xx status. Respond quickly (the request times out after 10 seconds).
  • Any other result, or a timeout, is retried after 1 minute, 5 minutes, 30 minutes and 2 hours. The fifth failed attempt is final.
  • After 10 consecutive failures the subscription is disabled (disabledAt is set in GET /hooks). Call POST /hooks/:id/rotate to issue a new secret and re-enable it.
  • Delivery is at-least-once. De-duplicate on the envelope id.

(Retry timing is taken from the Slingvo API contract and has not been observed on live deliveries.)


9. Verifying webhook signatures

Each delivery is signed so you can confirm it came from Slingvo and was not altered.

X-Slingvo-Signature: t=1790000000,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e6e2d1e5d5c1e0f5e2b1c
  • t is the Unix time in seconds when Slingvo signed the delivery.
  • v1 is the lowercase hex HMAC-SHA256 of the string t + "." + rawBody, using the subscription's whsec_... secret as the key.
  • rawBody is the exact bytes of the request body, before any JSON parsing. Re-serializing parsed JSON will not produce the same bytes.

To verify:

  1. Split the header on , and read t and v1.
  2. Reject the request if t differs from the current time by more than 300 seconds (replay protection).
  3. Compute the HMAC over `${t}.${rawBody}`.
  4. Compare with v1 using a constant-time comparison. Reject on mismatch.

HTTP header names are case-insensitive; your framework may lower-case X-Slingvo-Signature.

Node.js example

const crypto = require("crypto");

function verifySlingvoSignature(rawBody, signatureHeader, secret, toleranceSeconds = 300) {
  if (!signatureHeader) return false;

  const parts = {};
  for (const piece of signatureHeader.split(",")) {
    const [k, v] = piece.split("=");
    parts[k.trim()] = (v || "").trim();
  }
  if (!parts.t || !parts.v1) return false;

  const ageSeconds = Math.abs(Date.now() / 1000 - Number(parts.t));
  if (!Number.isFinite(ageSeconds) || ageSeconds > toleranceSeconds) return false;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(parts.t + "." + rawBody)
    .digest("hex");

  const a = Buffer.from(expected);
  const b = Buffer.from(parts.v1);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// Express: capture the raw body
// app.post("/slingvo", express.raw({ type: "application/json" }), (req, res) => {
//   const ok = verifySlingvoSignature(
//     req.body.toString("utf8"),
//     req.get("X-Slingvo-Signature"),
//     process.env.SLINGVO_WEBHOOK_SECRET
//   );
//   if (!ok) return res.sendStatus(401);
//   res.sendStatus(200);
// });

Python example

import hmac, hashlib, time

def verify_slingvo_signature(raw_body: bytes, header: str, secret: str, tolerance=300) -> bool:
    if not header:
        return False
    parts = dict(p.strip().split("=", 1) for p in header.split(",") if "=" in p)
    t, v1 = parts.get("t"), parts.get("v1")
    if not t or not v1:
        return False
    try:
        if abs(time.time() - int(t)) > tolerance:
            return False
    except ValueError:
        return False
    expected = hmac.new(secret.encode(), t.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, v1)

Zapier users

If you use the Slingvo triggers in Zapier, Zapier subscribes for you and the integration performs this check internally. In a Zapier code step you would read the raw request from bundle.rawRequest (headers and body). Zapier may rename the signature header, for example to Http-X-Slingvo-Signature. The signature check inside Zapier has not yet been exercised with a live Zapier delivery.


10. Zapier triggers, actions and fields

Triggers

Trigger Event Notes
New Contact contact.created Has an option, "Ignore changes made through the API / Zapier?", to avoid loops.
Updated Contact contact.updated Same option as above.
Disposition Logged disposition.logged Includes the contact card.
Contact Sent to CRM contact.sent_to_crm Hidden until verified end to end.

Actions and searches

Name Type Endpoint
Create Contact Action POST /contacts
Update Contact Action PATCH /contacts/:id
Find Contact Search GET /contacts/find
Find or Create Contact Search + action GET /contacts/find, then POST /contacts

Custom fields appear as extra input fields (Create/Update) and as customFields__<name> outputs once a sample contact contains them.

Fields available in a Zap

Zapier flattens the webhook payload into simple fields so they are easy to map into another app.

Field Description
contactId Slingvo contact id
fullName, firstName, lastName Name. First and last are derived from fullName.
email, phone Primary email and best phone number
allEmails, allPhones All values, comma-separated
emails[], phones[] Full lists
address, address2, city, state, zip, fullAddress Property address
mailingAddress, mailingAddress2, mailingCity, mailingState, mailingZip Mailing address
source, tags (comma-separated), notes (joined), description, status, disposition Contact details
customFields Object of custom values
createdAt, updatedAt Timestamps
id, eventType, eventCreatedAt, origin, apiKeyId Event details
dispositionLogId, dispositionId, disposition, dispositionValue, appliedById, folderId, dispositionSource Disposition Logged only
sentAt, crmSource, eventId Contact Sent to CRM only

11. Data fields sent to other CRMs

What a CRM receives depends on the Zap you build: you choose which Slingvo fields to map to which CRM fields. Slingvo makes these fields available.

Slingvo field Content Typical CRM destination (your mapping)
fullName (and derived firstName, lastName) Contact name Name fields
email / emails[] Email addresses (primary flagged) Email
phone / phones[] Phone numbers in E.164, with type (MOBILE, TELEPHONE, HOME, WORK) and best-number flag Phone fields
address, address2, city, state, zip Property address Address
mailingAddress…mailingZip Mailing address Mailing address, if the CRM has one
source Lead source Source
tags Tags Tags / categories
notes Notes entered in Slingvo Note
disposition The contact's current disposition label Stage, status, tag or note
customFields Values of your custom fields Custom fields
createdAt, updatedAt Timestamps Optional

Not sent: internal owner ids, secrets, dialer status, call records.

Do-not-call information is available in the phones[].isDnc field of the contact object (and in Zapier as part of phones[]). It is not included in the contact.sent_to_crm payload, so your CRM will not know a number is flagged in Slingvo unless you map it from another trigger.

Every CRM names its fields differently. Slingvo has not verified field mappings for any specific CRM, so check your own CRM's field list in the Zap editor.


12. Limitations

  • Single name field. Slingvo stores one fullName. First and last names are derived by splitting at the first space.
  • Disposition and status are read-only through the API. You can read them on contacts and in events, but cannot set them with POST or PATCH.
  • Nothing is deleted by an update. Phones, emails and notes can only be added. Blank values are ignored and do-not-call flags are never cleared.
  • Tags are replaced on update when you send tags.
  • No contact delete endpoint.
  • Contacts created without listId are owned by the admin and not visible to agents.
  • Webhooks require HTTPS.
  • Rate limit: 60 requests per minute per key.

When you contact support, include the approximate time (with time zone) of the request, the endpoint, and the HTTP status and error.code. Never send your API key or webhook secret.

© Slingvo. Documentation for Public API v1.