Overview
Webhooks allow you to receive real-time notifications when events occur, such as incoming messages, delivery confirmations, and status updates. The Contiguity SDK provides utilities to verify webhook signatures and parse webhook payloads.
Quick Example
import { Contiguity } from 'contiguity' ;
const contiguity = new Contiguity ( 'contiguity_sk_...' );
// In your webhook endpoint
app . post ( '/webhooks/contiguity' , ( req , res ) => {
// Verify the webhook signature
const isValid = contiguity . webhook . verify ( req , 'your_webhook_secret' );
if ( ! isValid ) {
return res . status ( 401 ). send ( 'Invalid signature' );
}
// Parse the webhook payload
const event = contiguity . webhook . parse ( req . body );
console . log ( 'Event type:' , event . type );
console . log ( 'Event data:' , event . data );
res . status ( 200 ). send ( 'OK' );
});
Verifying Webhook Signatures
Contiguity signs all webhook requests with a signature that you should verify before processing the payload. This ensures the webhook actually came from Contiguity.
Automatic Verification (Request Object)
The easiest way to verify is to pass the entire request object:
const isValid = contiguity . webhook . verify ( req , 'your_webhook_secret' );
if ( ! isValid ) {
return res . status ( 401 ). send ( 'Invalid signature' );
}
This method automatically extracts the signature from the contiguity-signature header and the raw body from the request.
Manual Verification (Raw Data)
For more control, you can verify manually with the raw body and signature:
const rawBody = req . body ; // Must be raw string or Buffer
const signature = req . headers [ 'contiguity-signature' ];
const secret = 'your_webhook_secret' ;
const isValid = contiguity . webhook . verify ( rawBody , signature , secret );
With Tolerance Window
Add a tolerance window (in seconds) to account for clock skew:
// Allow up to 5 minutes of clock skew
const isValid = contiguity . webhook . verify ( req , 'your_webhook_secret' , 300 );
Parsing Webhook Payloads
After verifying the signature, parse the webhook body to get a typed event object:
const event = contiguity . webhook . parse ( req . body );
console . log ( 'Event type:' , event . type );
console . log ( 'Event ID:' , event . id );
console . log ( 'Timestamp:' , event . timestamp );
console . log ( 'Event data:' , event . data );
Webhook Event Types
Contiguity sends different types of webhook events:
text.incoming.sms / text.incoming.mms
Incoming SMS or MMS message received {
type : 'text.incoming.sms' , // or 'text.incoming.mms'
id : 'evt_...' ,
timestamp : 1234567890 ,
api_version : 'v2' ,
data : {
from : '+1234567890' ,
to : '+19876543210' ,
body : 'Hello!' ,
timestamp : 1234567890 ,
attachments : [] // Only for MMS
}
}
Incoming iMessage received {
type : 'imessage.incoming' ,
id : 'evt_...' ,
timestamp : 1234567890 ,
api_version : 'v2' ,
data : {
from : '+1234567890' ,
to : '+19876543210' ,
body : 'Hello from iMessage!' ,
timestamp : 1234567890 ,
attachments : []
}
}
text.delivery.confirmed / text.delivery.failed
Message delivery status update {
type : 'text.delivery.confirmed' , // or 'text.delivery.failed'
id : 'evt_...' ,
timestamp : 1234567890 ,
api_version : 'v2' ,
data : {
from : '+1234567890' ,
to : '+19876543210' ,
message_id : 'msg_...' ,
timestamp : 1234567890 ,
error : 'Delivery failed' // Only for failed events
}
}
Complete Webhook Handler
Here’s a complete example of handling webhooks with Express:
import express from 'express' ;
import { Contiguity } from 'contiguity' ;
const app = express ();
const contiguity = new Contiguity ( 'contiguity_sk_...' );
const WEBHOOK_SECRET = process . env . WEBHOOK_SECRET ;
// IMPORTANT: Use raw body parser for webhook verification
app . use ( '/webhooks' , express . raw ({ type: 'application/json' }));
app . post ( '/webhooks/contiguity' , async ( req , res ) => {
try {
// 1. Verify the webhook signature
const isValid = contiguity . webhook . verify ( req , WEBHOOK_SECRET );
if ( ! isValid ) {
console . log ( 'Invalid webhook signature' );
return res . status ( 401 ). send ( 'Invalid signature' );
}
// 2. Parse the webhook payload
const event = contiguity . webhook . parse ( req . body );
// 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 'text.delivery.confirmed' :
case 'text.delivery.failed' :
await handleDeliveryStatus ( event );
break ;
default :
console . log ( 'Unknown event type:' , event . type );
}
// 4. Respond quickly
res . status ( 200 ). send ( 'OK' );
} catch ( error ) {
console . error ( 'Webhook error:' , error );
res . status ( 500 ). send ( 'Internal server error' );
}
});
async function handleIncomingText ( event ) {
console . log ( 'Incoming text:' , event . data );
// Auto-reply example
await contiguity . text . send ({
to: event . data . from ,
from: event . data . to ,
message: `You said: ${ event . data . message } `
});
}
async function handleIncomingImessage ( event ) {
console . log ( 'Incoming iMessage:' , event . data );
// Use the reply helper
await contiguity . imessage . reply ( event , {
message: 'Thanks for your message!'
});
}
async function handleDeliveryStatus ( event ) {
console . log ( 'Delivery update:' , event . data );
// Update your database with delivery status
// await db.messages.update(event.data.message_id, {
// status: event.data.status
// });
}
app . listen ( 3000 , () => {
console . log ( 'Webhook server running on port 3000' );
});
Framework Examples
Next.js API Route
// pages/api/webhooks/contiguity.ts
import type { NextApiRequest , NextApiResponse } from 'next' ;
import { Contiguity } from 'contiguity' ;
const contiguity = new Contiguity ( process . env . CONTIGUITY_API_KEY ! );
export const config = {
api: {
bodyParser: false , // Required for signature verification
},
};
export default async function handler (
req : NextApiRequest ,
res : NextApiResponse
) {
if ( req . method !== 'POST' ) {
return res . status ( 405 ). send ( 'Method not allowed' );
}
// Get raw body
const rawBody = await getRawBody ( req );
// Verify signature
const signature = req . headers [ 'contiguity-signature' ] as string ;
const isValid = contiguity . webhook . verify (
rawBody ,
signature ,
process . env . WEBHOOK_SECRET !
);
if ( ! isValid ) {
return res . status ( 401 ). send ( 'Invalid signature' );
}
// Parse and handle event
const event = contiguity . webhook . parse ( rawBody );
console . log ( 'Received event:' , event . type );
res . status ( 200 ). send ( 'OK' );
}
async function getRawBody ( req : NextApiRequest ) : Promise < Buffer > {
return new Promise (( resolve , reject ) => {
const chunks : Buffer [] = [];
req . on ( 'data' , ( chunk ) => chunks . push ( chunk ));
req . on ( 'end' , () => resolve ( Buffer . concat ( chunks )));
req . on ( 'error' , reject );
});
}
Fastify
import Fastify from 'fastify' ;
import { Contiguity } from 'contiguity' ;
const fastify = Fastify ();
const contiguity = new Contiguity ( process . env . CONTIGUITY_API_KEY ! );
fastify . post ( '/webhooks/contiguity' , {
config: {
rawBody: true , // Enable raw body
},
handler : async ( request , reply ) => {
const isValid = contiguity . webhook . verify (
request . rawBody ,
request . headers [ 'contiguity-signature' ],
process . env . WEBHOOK_SECRET !
);
if ( ! isValid ) {
return reply . code ( 401 ). send ( 'Invalid signature' );
}
const event = contiguity . webhook . parse ( request . rawBody );
console . log ( 'Event:' , event . type );
return reply . code ( 200 ). send ( 'OK' );
},
});
fastify . listen ({ port: 3000 });
Responding to Webhooks
Use the reply() helper methods to quickly respond to incoming messages:
// Text message reply
await contiguity . text . reply ( event , {
message: 'Thanks for your message!'
});
// iMessage reply
await contiguity . imessage . reply ( event , {
message: 'Got your iMessage!' ,
attachments: [ 'https://example.com/image.jpg' ]
});
// WhatsApp reply
await contiguity . whatsapp . reply ( event , {
message: 'Received!'
});
The reply() methods automatically swap the to and from fields from the webhook event, so you don’t need to manually specify them.
Security Best Practices
Always Verify Never process webhooks without verifying the signature first.
Use HTTPS Always use HTTPS for your webhook endpoints in production.
Keep Secrets Safe Store webhook secrets in environment variables, never in code.
Respond Quickly Respond with 200 OK as quickly as possible. Do heavy processing async.
Troubleshooting
Signature verification failing
Ensure you’re using the raw request body, not parsed JSON
Check that the webhook secret matches the one in your Contiguity dashboard
Verify your server’s clock is synchronized (for timestamp validation)
Check that you’re reading the contiguity-signature header correctly
Webhooks not being received
Verify your webhook URL is publicly accessible
Check that your endpoint responds with 200 OK
Ensure your server is not timing out
Check firewall and security group settings
Implement idempotency using the event id field
Store processed event IDs to prevent duplicate processing
Webhooks may be retried if your server doesn’t respond quickly
Next Steps
Text Messages Learn about replying to text messages
iMessage Learn about replying to iMessages
WhatsApp Learn about replying to WhatsApp messages
API Reference View complete webhook API documentation