WebhookResource
The webhook resource provides methods for verifying webhook signatures and parsing webhook payloads from Contiguity.
verify()
contiguity.webhook.verify(
rawBodyOrReq: string | Buffer | WebhookRequestLike,
signatureHeaderOrSecret?: string,
secretOrTolerance?: string | number,
toleranceSeconds?: number
): boolean
Verify the HMAC-SHA256 signature of a webhook request to ensure it came from Contiguity.
This method has two calling patterns:
Pattern 1: Pass request object
rawBodyOrReq
WebhookRequestLike
required
The incoming request object with body or rawBody and headers properties.
Your webhook secret. If not provided, signature verification will fail.
Tolerance in seconds for timestamp validation. Helps prevent replay attacks.
app.post('/webhook', (req, res) => {
const isValid = contiguity.webhook.verify(
req,
'your_webhook_secret',
300 // 5 minutes tolerance
);
if (!isValid) {
return res.status(401).send('Invalid signature');
}
// Process webhook...
res.sendStatus(200);
});
Pattern 2: Pass raw body and signature
The raw request body as received (do not parse or modify).
The value of the Contiguity-Signature header.
Tolerance in seconds for timestamp validation.
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['contiguity-signature'];
const isValid = contiguity.webhook.verify(
req.body, // raw buffer
signature,
'your_webhook_secret',
300
);
if (!isValid) {
return res.status(401).send('Invalid signature');
}
// Process webhook...
res.sendStatus(200);
});
parse()
contiguity.webhook.parse(rawBody: string | Buffer): WebhookEventBase
Parse a raw webhook body into a typed event object.
Unique identifier for the webhook event.
The type of webhook event (e.g., text.incoming.sms, imessage.incoming, email.incoming).
Unix timestamp (in seconds) when the event occurred.
API version that generated this event.
Event-specific data payload.
app.post('/webhook', express.json(), (req, res) => {
const event = contiguity.webhook.parse(req.body);
console.log('Event ID:', event.id);
console.log('Event type:', event.type);
console.log('Timestamp:', new Date(event.timestamp * 1000));
console.log('Data:', event.data);
res.sendStatus(200);
});
Complete Example
import express from 'express';
import { Contiguity } from 'contiguity';
const app = express();
const contiguity = new Contiguity('contiguity_sk_...');
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
app.post('/webhook',
express.raw({ type: 'application/json' }),
async (req, res) => {
// Step 1: Verify signature
const signature = req.headers['contiguity-signature'];
const isValid = contiguity.webhook.verify(
req.body,
signature,
WEBHOOK_SECRET,
300 // 5 minute tolerance
);
if (!isValid) {
console.error('Invalid webhook signature');
return res.status(401).send('Unauthorized');
}
// Step 2: Parse the event
const event = contiguity.webhook.parse(req.body);
console.log('Received event:', event.type);
// Step 3: Handle different event types
switch (event.type) {
case 'text.incoming.sms':
case 'text.incoming.mms':
await handleIncomingText(event);
break;
case 'imessage.incoming':
await handleIncomingImessage(event);
break;
case 'email.incoming':
await handleIncomingEmail(event);
break;
case 'text.delivery.confirmed':
console.log('Message delivered:', event.data.message_id);
break;
case 'text.delivery.failed':
console.log('Message failed:', event.data.message_id);
break;
default:
console.log('Unhandled event type:', event.type);
}
res.sendStatus(200);
}
);
async function handleIncomingText(event) {
const { from, to, body } = event.data;
console.log(`Text from ${from}: ${body}`);
// Reply to the message
await contiguity.text.reply(event, {
message: 'Thanks for your message!'
});
}
async function handleIncomingImessage(event) {
const { from, to, body } = event.data;
console.log(`iMessage from ${from}: ${body}`);
// Reply to the message
await contiguity.imessage.reply(event, {
message: 'Thanks for your iMessage!'
});
}
async function handleIncomingEmail(event) {
const { from, to, subject } = event.data;
console.log(`Email from ${from}: ${subject}`);
}
app.listen(3000, () => {
console.log('Webhook server listening on port 3000');
});
Event Types
The type field can be one of:
text.incoming.sms - Incoming SMS message
text.incoming.mms - Incoming MMS message with attachments
text.delivery.confirmed - Text message successfully delivered
text.delivery.failed - Text message delivery failed
imessage.incoming - Incoming iMessage
email.incoming - Incoming email
otp.reverse.verified - Reverse OTP verification completed
numbers.substitution - Phone number was substituted
identity.verification_session.started - Identity verification started
identity.verification_session.verified - Identity successfully verified
identity.verification_session.failed - Identity verification failed
Security Best Practices
- Always verify signatures - Never trust webhook data without verification
- Use HTTPS endpoints - Webhooks should only be sent to secure URLs
- Set a reasonable tolerance - 300 seconds (5 minutes) is a good default
- Keep secrets secure - Store webhook secrets in environment variables
- Use raw body - Don’t parse or modify the request body before verification
- Handle errors gracefully - Always respond with 200 even if processing fails
// Good: Use raw body parser
app.post('/webhook',
express.raw({ type: 'application/json' }),
handleWebhook
);
// Bad: JSON parser modifies the body
app.post('/webhook',
express.json(), // This breaks signature verification!
handleWebhook
);
Framework-Specific Examples
Express
import express from 'express';
app.post('/webhook',
express.raw({ type: 'application/json' }),
(req, res) => {
const isValid = contiguity.webhook.verify(
req.body,
req.headers['contiguity-signature'],
process.env.WEBHOOK_SECRET
);
// ...
}
);
Next.js
// pages/api/webhook.ts
export const config = {
api: {
bodyParser: false, // Disable automatic parsing
},
};
export default async function handler(req, res) {
const rawBody = await getRawBody(req);
const isValid = contiguity.webhook.verify(
rawBody,
req.headers['contiguity-signature'],
process.env.WEBHOOK_SECRET
);
// ...
}
Fastify
import Fastify from 'fastify';
fastify.post('/webhook',
{ config: { rawBody: true } },
async (req, reply) => {
const isValid = contiguity.webhook.verify(
req.rawBody,
req.headers['contiguity-signature'],
process.env.WEBHOOK_SECRET
);
// ...
}
);