Consuming Modem Pay Webhooks with TypeScript & Express

Webhooks let your server react to events in real time: when a customer signs up, a payment succeeds, or a charge is updated, Modem Pay sends an HTTP request to your endpoint. Rather than polling the API, you receive notifications instantly, enabling you to automate order processing, update user records, or trigger emails the moment something happens.
Prerequisites
Node.js (>=14) and npm or yarn
TypeScript is configured in your project
Express web framework
modem-pay SDK installed:
npm install modem-pay express body-parser # or yarn add modem-pay express body-parser
A webhook secret generated in the Modem Pay dashboard (Developers → Webhooks) and set as an environment variable:
export MODEM_WEBHOOK_SECRET="whXXXXXXXXXXXXXXXXXXXX"
1. Define Your Webhook Endpoint
Create a dedicated route to receive POST requests from Modem Pay. In Express + TypeScript:
// src/webhook.ts
import express, { Request, Response } from 'express';
import bodyParser from 'body-parser';
import ModemPay from 'modem-pay';
const app = express();
// Parse JSON body for access to raw payload
app.use(bodyParser.json());
const modempay = new ModemPay();
const WEBHOOK_SECRET = process.env.MODEM_WEBHOOK_SECRET!;
app.post('/webhook', async (req: Request, res: Response) => {
const payload = req.body;
const signature = req.headers['x-modem-signature'] as string;
try {
// Validate and parse the event
const event = modempay.webhooks.composeEventDetails(
payload,
signature,
WEBHOOK_SECRET
);
// Delegate to specific handlers
await handleEvent(event.event, event.payload);
// Acknowledge receipt quickly
res.status(200).send('Received');
} catch (err) {
console.error('⚠️ Webhook signature verification failed:', err);
res.status(400).send('Invalid signature');
}
});
export default app;
Then wire up and start your server:
// src/index.ts
import app from './webhook';
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`🚀 Webhook listener running on port ${PORT}`);
});
2. Signature Validation
Modem Pay signs each webhook with a header x-modem-signature
. The SDK’s composeEventDetails
method:
Verifies the signature against your secret.
Parses and returns an
Event
object:interface Event<T = any> { event: string; // e.g. "charge.succeeded" payload: T; // event-specific data }
Always wrap this call in a try
/catch
to reject invalid requests.
3. Handling Events: Real-World Examples
Below is a sample handleEvent
function illustrating common use cases in an e-commerce context.
// src/handlers.ts
import { Event, EventType } from 'modem-pay';
export async function handleEvent(
eventType: EventType,
payload: any
): Promise<void> {
switch (eventType) {
case 'customer.created':
await onCustomerCreated(payload);
break;
case 'payment_intent.created':
await onPaymentIntentCreated(payload);
break;
case 'charge.succeeded':
await onChargeSucceeded(payload);
break;
default:
console.log(`🔔 Unhandled event type: ${eventType}`);
}
}
// Real‐world: Add new customer to your database
async function onCustomerCreated(customer: any) {
// e.g., save to MongoDB
await db.collection('customers').insertOne({
id: customer.id,
email: customer.email,
createdAt: new Date(customer.createdAt),
});
console.log(`✅ Customer ${customer.id} saved.`);
}
// Real‐world: Update order status when payment is captured
async function onChargeSucceeded(charge: any) {
const orderId = charge.metadata.order_id;
// e.g., update order in SQL db
await sql.query(
'UPDATE orders SET status = ? WHERE id = ?',
['paid', orderId]
);
console.log(`✅ Order ${orderId} marked as paid.`);
// Optionally, send notification email
await emailService.sendReceipt(charge.receipt_email, orderId);
}
You can extend this with additional handlers for inventory adjustments, CRM updates, SMS notifications, or accounting entries.
4. Testing Locally with the Modem Pay CLI
First, install the official CLI package:
# Install the Modem Pay CLI globally
npm install -g modempay-cli
# or with yarn
yarn global add modempay-cli
Then, authenticate in test mode and start forwarding webhooks to your local server:
# Log in (you’ll be prompted for your email and password)
modempay login
# Forward all incoming webhooks to your local endpoint
modempay listen --forward-url=http://localhost:3000/webhook
# → You’ll see a public test URL and a test secret printed
Once listening, you can simulate events to verify your handlers:
# Trigger a successful charge event
modempay trigger charge.succeeded
Check your server logs, your /webhook
endpoint should receive and process the test payload exactly as in production.
Conclusion
Following this guide, you can securely receive and react to Modem Pay webhook events in TypeScript and Express. You’ll validate each payload’s signature, dispatch events to dedicated handlers, and integrate real-world business logic, whether updating orders, onboarding customers, or notifying users. Local testing with the CLI ensures your integration is rock-solid before going live.
Subscribe to my newsletter
Read articles from Caleb Okpara directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Caleb Okpara
Caleb Okpara
I build stuff, fix stuff, and make sure payments actually work. Mostly coding, sometimes dealing with servers, and occasionally pretending to be a business guy.