Salesloft webhooks events
'Implement SalesLoft webhook handling with signature verification and event routing.From its SKILL.md
npx -y skills add jeremylongshore/claude-code-plugins-plus-skills --skill salesloft-webhooks-eventsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
What its file declares
Copied from the file, not written here
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
5.0 KB, ~1.1k tokens by cl100k_base, as published. Nobody here has run it
SalesLoft Webhooks & Events
Overview
Handle SalesLoft webhook notifications for real-time data sync. SalesLoft sends webhooks for person updates, email events (sent, opened, clicked, replied, bounced), call completions, and cadence membership changes. Webhooks use HMAC-SHA256 signatures.
Instructions
Step 1: Register Webhook in SalesLoft
Configure webhooks in SalesLoft Settings > Integrations > Webhooks:
- URL:
https://your-app.com/webhooks/salesloft - Events: Select specific events (person.updated, email.sent, etc.)
- Copy the webhook signing secret
Step 2: Signature Verification
import crypto from 'crypto';
import express from 'express';
function verifySalesloftWebhook(
rawBody: Buffer,
signature: string,
timestamp: string,
): boolean {
const secret = process.env.SALESLOFT_WEBHOOK_SECRET!;
// Replay protection: reject webhooks older than 5 minutes
const age = Math.abs(Date.now() / 1000 - parseInt(timestamp));
if (age > 300) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${rawBody.toString()}`)
.digest('hex');
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
Step 3: Event Router
interface SalesloftWebhookEvent {
event_type: string; // e.g., 'person.created', 'email.sent', 'call.completed'
event_id: string;
data: Record<string, any>;
created_at: string;
}
const handlers: Record<string, (data: any) => Promise<void>> = {
'person.created': async (data) => {
console.log(`New person: ${data.email_address}`);
await syncToExternalCRM(data);
},
'person.updated': async (data) => {
await updateExternalCRM(data.id, data);
},
'email.sent': async (data) => {
await logActivity('email_sent', data);
},
'email.opened': async (data) => {
await logActivity('email_opened', data);
},
'email.clicked': async (data) => {
await logActivity('email_clicked', data);
},
'email.replied': async (data) => {
await logActivity('email_replied', data);
await notifySalesRep(data.person_id, 'Reply received!');
},
'email.bounced': async (data) => {
await markEmailInvalid(data.person_id);
},
'call.completed': async (data) => {
await logActivity('call', { ...data, duration: data.duration });
},
};
Step 4: Express Webhook Endpoint
const app = express();
app.post('/webhooks/salesloft',
express.raw({ type: 'application/json' }),
async (req, res) => {
const sig = req.headers['x-salesloft-signature'] as string;
const ts = req.headers['x-salesloft-timestamp'] as string;
if (!verifySalesloftWebhook(req.body, sig, ts)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const event: SalesloftWebhookEvent = JSON.parse(req.body.toString());
// Idempotency: skip already-processed events
if (await isProcessed(event.event_id)) {
return res.status(200).json({ status: 'already_processed' });
}
// Respond immediately, process async
res.status(200).json({ received: true });
try {
const handler = handlers[event.event_type];
if (handler) {
await handler(event.data);
await markProcessed(event.event_id);
}
} catch (err) {
console.error(`Failed: ${event.event_type} ${event.event_id}`, err);
await queueForRetry(event);
}
}
);
Step 5: Idempotency Store
import { Redis } from 'ioredis';
const redis = new Redis(process.env.REDIS_URL!);
async function isProcessed(eventId: string): Promise<boolean> {
return (await redis.exists(`sl:event:${eventId}`)) === 1;
}
async function markProcessed(eventId: string): Promise<void> {
await redis.set(`sl:event:${eventId}`, '1', 'EX', 604800); // 7-day TTL
}
Error Handling
| Issue | Cause | Solution |
|---|---|---|
| Invalid signature | Wrong secret or body parsing | Use raw body parser, verify secret |
| Duplicate events | Webhook retries | Idempotency check by event_id |
| Timeout on processing | Heavy handler logic | Respond 200 immediately, process async |
| Missing events | Wrong event subscription | Check webhook config in SalesLoft dashboard |
Resources
Next Steps
For performance optimization, see salesloft-performance-tuning.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.