SDK Reference
Webhook verification
The SDK provides client.webhooks.constructEvent() to verify webhook signatures and parse the payload. It throws StaffifyError if the signature is invalid or the timestamp is more than 5 minutes old.
Signature format
Staffify signs every webhook request with HMAC-SHA256. The signature is sent in the X-Staffify-Signature header as sha256=<hex>. The timestamp is in X-Staffify-Timestamp (Unix seconds). The signed content is timestamp.body.
Express example
TypeScript
import express from 'express';
import Staffify, { StaffifyError } from 'staffify';
const app = express();
const client = new Staffify({ apiKey: process.env.STAFFIFY_API_KEY });
// Use raw body — must come before express.json()
app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
try {
const payload = client.webhooks.constructEvent(
req.body, // Buffer
req.headers['x-staffify-signature'] as string,
req.headers['x-staffify-timestamp'] as string,
process.env.WEBHOOK_SECRET!
);
switch (payload.event) {
case 'call.ended':
console.log('Call ended:', payload.data);
break;
case 'call.recording_ready':
console.log('Recording ready:', payload.data);
break;
}
res.json({ received: true });
} catch (err) {
if (err instanceof StaffifyError) {
console.error('Webhook verification failed:', err.message);
return res.status(400).json({ error: err.message });
}
throw err;
}
});Fastify example
TypeScript
import Fastify from 'fastify';
import Staffify, { StaffifyError } from 'staffify';
const app = Fastify();
const client = new Staffify({ apiKey: process.env.STAFFIFY_API_KEY });
app.addContentTypeParser('application/json', { parseAs: 'buffer' }, (_req, body, done) => {
done(null, body);
});
app.post('/webhooks', async (req, reply) => {
try {
const payload = client.webhooks.constructEvent(
req.body as Buffer,
req.headers['x-staffify-signature'] as string,
req.headers['x-staffify-timestamp'] as string,
process.env.WEBHOOK_SECRET!
);
console.log('Event:', payload.event, payload.data);
return { received: true };
} catch (err) {
if (err instanceof StaffifyError) {
return reply.status(400).send({ error: err.message });
}
throw err;
}
});Important
- Always use the raw request body — not the parsed JSON. Parsing and re-serializing changes whitespace and key order, which breaks the signature.
- Never skip signature verification — always call constructEvent(), even in development.
- The 5-minute timestamp window protects against replay attacks.
- Rotate webhook secrets from client.webhooks.rotate(webhookId) or for agent webhooks with client.agents.rotateWebhookSecret(agentId).
Getting your webhook secret
Your secret is shown once when you create a webhook or rotate it. For agent webhooks, use client.agents.rotateWebhookSecret(). For org webhooks, use client.webhooks.rotate().