Consuming Modem Pay Webhooks with TypeScript & Express

Caleb OkparaCaleb Okpara
4 min read

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:

  1. Verifies the signature against your secret.

  2. 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.

0
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.