Gareth Wilson Gareth Wilson

Guide to Salesloft Webhooks: Features and Best Practices

Published


Salesloft webhooks tell your systems when something changes in a team's sales engagement data: a person is created or updated, a call is logged, an email changes, a meeting is booked, or someone is added to a cadence. If you build on Salesloft, webhooks are how that activity reaches your CRM sync, analytics and automation without polling the API.

This guide covers how Salesloft webhook subscriptions are created and signed, the event types and the scopes they need, the gaps in Salesloft's documentation, and the best practices for running a receiver in production.

What are Salesloft webhooks?

A Salesloft webhook subscription pairs one event type with a callback URL and a callback_token, a string you choose. Subscriptions are made on behalf of a team, and there is no dashboard for them: you create them through the Webhook Subscriptions API with an OAuth token or API key.

When a subscribed event fires, Salesloft sends an HTTP POST to the callback URL. The body is the affected resource, for example the new person object for person_created. Two headers come with it:

  • x-salesloft-event names the event type that triggered the delivery.
  • x-salesloft-signature is the HMAC hex digest of the body, generated with SHA-1 and the subscription's callback_token as the key.

Salesloft's docs describe the signature as covering "the response body", which in context means the body of the request Salesloft sends. Salesloft also includes the callback_token in each payload so a receiver can compare it directly, but the HMAC header is the stronger check, because it proves the body has not changed.

Salesloft webhook features

FeatureDetails
ConfigurationAPI only, through the Webhook Subscriptions API. No dashboard
Subscription modelOne event_type per subscription, made on behalf of a team
Event headerx-salesloft-event
Signature headerx-salesloft-signature
SchemeHMAC-SHA1, hex digest (40 characters), no prefix
Signed contentThe request body
SecretThe callback_token you set on the subscription
Replay windowNone. No timestamp is sent or signed
BodyThe resource object for the event. No envelope is documented
RetriesThree more attempts, 15 seconds apart, then marked as failed
IP allowlistNone published

Common events

Salesloft documents 37 event types. Most need a matching OAuth scope, and some also need user permissions such as View Team Calls or View All People.

AreaEventsScope
Peopleperson_created, person_updated, person_deleted, success_createdpeople:read
Accountsaccount_created, account_updated, account_deletedaccounts:read
Cadencescadence_created, cadence_updated, cadence_deleted, cadence_membership_created, cadence_membership_updated, step_created, step_updated, step_deletedcadences:read
Callscall_created, call_updated, call_data_record_created, call_data_record_updatedcalls:read
Conversationsconversation_created, conversation_recording_created, conversation_transcript_createdconversations:read
Emailemail_updated, link_swapemails:read
Email contentemail_with_body_and_subject_updatedemail_contents
Meetingsmeeting_booked, meeting_updatedmeetings:read
Notesnote_created, note_updated, note_deletednotes:read
Taskstask_created, task_updated, task_completed, task_deletedtasks:read
Usersuser_created, user_updatedteam:read
Bulk jobsbulk_job_completedNone listed

Salesloft describes each event as delivering the resource: "When a person is created, receive the new person object". The fields are those of the matching object in the Salesloft API reference. Salesloft publishes no example webhook body, so log the first delivery of each type before you rely on a field.

link_swap is different from the rest. It fires when an email is sent, carrying the links from the email body "so they may be replaced with new links before the email is sent". Salesloft's pages do not say how a replacement is returned, so check with Salesloft before building on it.

Inspect Salesloft webhooks as they arrive. Use a Hookdeck Inspect URL as a subscription's callback_url to inspect and replay real deliveries, with no account or setup required.

Setting up Salesloft webhooks

  1. Generate a long random callback_token, for example with openssl rand -hex 32, and store it server-side as SALESLOFT_CALLBACK_TOKEN.
  2. Make sure your OAuth app or API key has the scopes for the events you need.
  3. Create a subscription for each event type:
curl -X POST https://api.salesloft.com/v2/webhook_subscriptions \
  -H "Authorization: Bearer $SALESLOFT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "callback_url": "https://example.com/webhooks/salesloft",
    "callback_token": "'"$SALESLOFT_CALLBACK_TOKEN"'",
    "event_type": "person_created"
  }'

Repeat for person_updated, call_created, meeting_booked and any other event you need. All of them can point at the same URL, because x-salesloft-event tells your handler which one arrived. The same API lists, updates and deletes subscriptions.

If you use the same callback_token on every subscription that targets one URL, a single verifier covers them all. If you give each subscription its own token, your receiver has to know which token goes with which event.

If you receive Salesloft webhooks through Hookdeck, enter the callback_token as the Hookdeck source's secret.

Securing Salesloft webhooks

Compute the hex HMAC-SHA1 of the raw request body with your callback_token as the key, and compare it with x-salesloft-signature in constant time.

const crypto = require('crypto');
const express = require('express');

function verifySalesloftSignature(rawBody, signatureHeader, callbackToken) {
  if (!signatureHeader || !callbackToken) return false;
  const expected = crypto.createHmac('sha1', callbackToken).update(rawBody).digest('hex');
  const received = Buffer.from(signatureHeader.trim().toLowerCase(), 'utf8');
  const computed = Buffer.from(expected, 'utf8');
  return received.length === computed.length && crypto.timingSafeEqual(received, computed);
}

const app = express();

app.post('/webhooks/salesloft', express.raw({ type: '*/*' }), (req, res) => {
  if (!verifySalesloftSignature(req.body, req.get('x-salesloft-signature'), process.env.SALESLOFT_CALLBACK_TOKEN)) {
    return res.sendStatus(401);
  }

  const eventType = req.get('x-salesloft-event');
  const resource = JSON.parse(req.body.toString('utf8'));
  // Route on eventType, then hand off to a queue.
  res.sendStatus(200);
});

The same check in Python:

import hashlib
import hmac


def verify_salesloft_signature(raw_body: bytes, signature_header, callback_token: str) -> bool:
    if not signature_header or not callback_token:
        return False
    expected = hmac.new(callback_token.encode("utf-8"), raw_body, hashlib.sha1).hexdigest()
    return hmac.compare_digest(signature_header.strip().lower().encode("utf-8"), expected.encode("utf-8"))

To check a captured delivery by hand, printf '%s' "$BODY" | openssl dgst -sha1 -hmac "$TOKEN" should print the same digest as the header.

Make Salesloft webhooks production-ready. Hookdeck Event Gateway verifies the x-salesloft-signature header with your callback_token, stores every person, call and meeting event, and retries delivery for far longer than Salesloft's 45 seconds.

Salesloft webhook limitations and pain points

Retries are over in 45 seconds

The Problem: A deploy, a restart or a brief outage on your side loses events for good.

Why It Happens: Salesloft retries a failing webhook three more times, 15 seconds apart, and then marks it as failed. That covers about 45 seconds.

Workarounds:

  • Return 200 as soon as the signature checks out, and do the work afterwards.
  • Run a periodic sync through the Salesloft API to catch anything a webhook missed.

How Hookdeck Can Help: Hookdeck accepts each delivery as soon as it arrives and stores it, so Salesloft's attempts succeed even during your downtime. Hookdeck then delivers with automatic retries you configure and lets you replay anything that still fails.

Undocumented payloads

The Problem: You write a handler from the API reference, and the webhook body turns out to differ.

Why It Happens: Salesloft says each event delivers the resource object, but publishes no example webhook body and does not say where in it the callback_token appears.

Workarounds:

  • Capture one real delivery per event type before you write the handler.
  • Read fields defensively, and fetch the resource from the API when you need guaranteed fields.

How Hookdeck Can Help: Hookdeck keeps the headers and raw body of every request, so your first live deliveries become fixtures you can inspect and replay against your handler.

SHA-1, keyed with your own token

The Problem: Verification fails even though the code looks right.

Why It Happens: Most webhook providers use SHA-256, so it is easy to copy a SHA-256 verifier. Using an OAuth token or API key as the HMAC key, rather than the callback_token, fails the same way.

Workarounds:

  • Use sha1 and the callback_token from the subscription.
  • Fetch the subscription from the API to confirm which token it uses.

How Hookdeck Can Help: Hookdeck's Salesloft source is configured for HMAC-SHA1 hex on x-salesloft-signature, so you only enter the token.

No event ID or timestamp

The Problem: A retried delivery looks identical to the original, and a captured request still verifies if it is sent again.

Why It Happens: Salesloft sends no delivery ID and signs no timestamp.

Workarounds:

  • Build an idempotency key from x-salesloft-event, the resource id and a field that changes with each update, such as an update timestamp if the resource has one.
  • Make updates in your systems upserts keyed on the Salesloft resource ID.

How Hookdeck Can Help: Hookdeck's deduplication can drop repeated deliveries based on the header and body fields you choose.

Best practices

Verify against the raw body

The signature covers the exact bytes Salesloft sent. In Express, use express.raw({ type: '*/*' }). In Next.js, use await request.text(). In FastAPI, use await request.body().

Route on x-salesloft-event

The event type is in the header, not documented in the body. Switch on the header and acknowledge any value you do not handle.

Request only the scopes you need

Each event needs its read scope, and the authorizing user needs the matching permissions or the data will not be included. Subscribe only to events your integration uses.

Acknowledge fast and process asynchronously

With 45 seconds of retries, slow handlers lose events. Verify, store, return 200, and process afterwards. See why to process webhooks asynchronously.

Make handlers idempotent

Retries can repeat a delivery. Key your processing on the event type and resource ID. See our guide to webhook idempotency.

Conclusion

Salesloft signs each webhook with a hex HMAC-SHA1 of the body in x-salesloft-signature, keyed with the callback_token you chose when you created the subscription, and names the event in x-salesloft-event. Create subscriptions through the API, verify against the raw bytes with SHA-1, route on the header, and acknowledge quickly, because Salesloft's retries are over in under a minute.

Hookdeck Event Gateway verifies every Salesloft signature, stores each event, and retries on your schedule, so a short outage does not cost you call and meeting data.

Get started with Hookdeck for free and handle Salesloft webhooks reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.