Handling Webhooks

Handling Checkout Webhooks

After the customer approves or cancels the transaction on the hosted checkout page, MMG will send a POST request to your configured Response URL.

This request contains an RSA-encrypted, Base64-URL encoded token. You must use the SDK to decrypt this token to verify the actual payment status before fulfilling any orders.

Usage Example

Below is an example of setting up a webhook listener in Node.js/Express.

1import { MmgCheckoutUtil } from "mobile-money-sdk";
2import express from "express";
3
4const app = express();
5app.use(express.json()); // Ensure your server can parse JSON bodies
6
7// Initialize the Checkout Utility
8/**
9 process.env.* returns string | undefined — TypeScript will complain if you pass those directly to fields typed as string. The ! non-null assertion (or
10 runtime validation) is needed for publicKey, secretKey, merchantId, and clientId.
11**/
12 const checkoutUtil = new MmgCheckoutUtil({
13 environment: "Live", // or "Sandbox"
14 publicKey: process.env.MMG_PUBLIC_KEY!,
15 privateKey: process.env.MMG_PRIVATE_KEY,
16 });
17
18// The URL you provide to MMG during onboarding (or auto-generated)
19app.post("/mmg-sdk/callback/:hash", async (req, res) => {
20 try {
21 // 1. Extract the encrypted token from the incoming request body
22 const encryptedToken = req.body.token;
23
24 if (!encryptedToken) {
25 return res.status(400).send("Missing token");
26 }
27
28 // 2. Decrypt the token using the SDK utility
29 const response = checkoutUtil.decryptWebhookToken(encryptedToken);
30
31 /* The decrypted response object looks like this:
32 {
33 merchantTransactionId: "ORD-1765472622",
34 transactionId: "999888777",
35 ResultCode: "0",
36 ResultMessage: "Transaction Successful",
37 htmlResponse: "..."
38 }
39 */
40
41 // 3. Process the Result
42 if (response.ResultCode === "0") {
43 // Payment was successful!
44 console.log(`Payment confirmed for order: ${response.merchantTransactionId}`);
45 // TODO: Update your database, mark order as paid, and fulfill goods.
46 } else {
47 // Payment failed, timed out, or was cancelled
48 console.warn(`Payment failed with code ${response.ResultCode}: ${response.ResultMessage}`);
49 // TODO: Mark order as failed in your database.
50 }
51
52 // 4. Always return a 200 OK status to MMG so they know you received the webhook
53 res.status(200).send("OK");
54
55 } catch (error) {
56 console.error("Webhook decryption failed. Invalid key or payload.", error);
57 // Still return 200 to prevent MMG from endlessly retrying a bad payload
58 res.status(200).send("Processed with errors");
59 }
60});