Guide to Salesloft Webhooks: Features and Best Practices
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-eventnames the event type that triggered the delivery.x-salesloft-signatureis the HMAC hex digest of the body, generated with SHA-1 and the subscription'scallback_tokenas 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
| Feature | Details |
|---|---|
| Configuration | API only, through the Webhook Subscriptions API. No dashboard |
| Subscription model | One event_type per subscription, made on behalf of a team |
| Event header | x-salesloft-event |
| Signature header | x-salesloft-signature |
| Scheme | HMAC-SHA1, hex digest (40 characters), no prefix |
| Signed content | The request body |
| Secret | The callback_token you set on the subscription |
| Replay window | None. No timestamp is sent or signed |
| Body | The resource object for the event. No envelope is documented |
| Retries | Three more attempts, 15 seconds apart, then marked as failed |
| IP allowlist | None 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.
| Area | Events | Scope |
|---|---|---|
| People | person_created, person_updated, person_deleted, success_created | people:read |
| Accounts | account_created, account_updated, account_deleted | accounts:read |
| Cadences | cadence_created, cadence_updated, cadence_deleted, cadence_, cadence_, step_created, step_updated, step_deleted | cadences:read |
| Calls | call_created, call_updated, call_data_record_created, call_data_record_updated | calls:read |
| Conversations | conversation_created, conversation_, conversation_ | conversations:read |
email_updated, link_swap | emails:read | |
| Email content | email_ | email_contents |
| Meetings | meeting_booked, meeting_updated | meetings:read |
| Notes | note_created, note_updated, note_deleted | notes:read |
| Tasks | task_created, task_updated, task_completed, task_deleted | tasks:read |
| Users | user_created, user_updated | team:read |
| Bulk jobs | bulk_job_completed | None 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_urlto inspect and replay real deliveries, with no account or setup required.
Setting up Salesloft webhooks
- Generate a long random
callback_token, for example withopenssl rand -hex 32, and store it server-side asSALESLOFT_CALLBACK_TOKEN. - Make sure your OAuth app or API key has the scopes for the events you need.
- 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-signatureheader with yourcallback_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
200as 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
sha1and thecallback_tokenfrom 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 resourceidand 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.