From salesloft-pack
Implements SalesLoft webhook handling with HMAC-SHA256 signature verification, replay protection, and event routing for person/email/call activities.
npx claudepluginhub jeremylongshore/claude-code-plugins-plus-skills --plugin salesloft-packThis skill is limited to using the following tools:
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.
Provides production reference architecture for SalesLoft API integrations: typed TypeScript client, service layer, webhook processing, CRM sync jobs, and project structure.
Handles Instantly.ai v2 webhook events for email campaigns (sent, opened, clicked, replied, bounced) and lead updates. Use for webhook endpoints or CRM sync pipelines.
Implements HubSpot webhook endpoints for CRM events like contact/deal changes, with v3 signature verification and idempotent handling in Express.
Share bugs, ideas, or general feedback.
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.
Configure webhooks in SalesLoft Settings > Integrations > Webhooks:
https://your-app.com/webhooks/salesloftimport 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));
}
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 });
},
};
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);
}
}
);
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
}
| 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 |
For performance optimization, see salesloft-performance-tuning.