> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://mmg-sdk.wetechnologygroup.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://mmg-sdk.wetechnologygroup.com/_mcp/server.

# Handling Webhooks

> How to receive and decrypt the asynchronous payment response from MMG.

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

```typescript
import { MmgCheckoutUtil } from "mobile-money-sdk"; 
import express from "express";

const app = express();
app.use(express.json()); // Ensure your server can parse JSON bodies

// Initialize the Checkout Utility
/** 
    process.env.* returns string | undefined — TypeScript will complain if you pass those directly to fields typed as string. The ! non-null assertion (or
    runtime validation) is needed for publicKey, secretKey, merchantId, and clientId.
**/
  const checkoutUtil = new MmgCheckoutUtil({
      environment: "Live", // or "Sandbox"
      publicKey: process.env.MMG_PUBLIC_KEY!,
      privateKey: process.env.MMG_PRIVATE_KEY,
  });

// The URL you provide to MMG during onboarding (or auto-generated)
app.post("/mmg-sdk/callback/:hash", async (req, res) => {
    try {
        // 1. Extract the encrypted token from the incoming request body
        const encryptedToken = req.body.token;

        if (!encryptedToken) {
            return res.status(400).send("Missing token");
        }

        // 2. Decrypt the token using the SDK utility
        const response = checkoutUtil.decryptWebhookToken(encryptedToken);

        /* The decrypted response object looks like this:
         {
           merchantTransactionId: "ORD-1765472622",
           transactionId: "999888777",
           ResultCode: "0",
           ResultMessage: "Transaction Successful",
           htmlResponse: "..."
         }
        */

        // 3. Process the Result
        if (response.ResultCode === "0") {
            // Payment was successful! 
            console.log(`Payment confirmed for order: ${response.merchantTransactionId}`);
            // TODO: Update your database, mark order as paid, and fulfill goods.
        } else {
            // Payment failed, timed out, or was cancelled
            console.warn(`Payment failed with code ${response.ResultCode}: ${response.ResultMessage}`);
            // TODO: Mark order as failed in your database.
        }

        // 4. Always return a 200 OK status to MMG so they know you received the webhook
        res.status(200).send("OK");

    } catch (error) {
        console.error("Webhook decryption failed. Invalid key or payload.", error);
        // Still return 200 to prevent MMG from endlessly retrying a bad payload
        res.status(200).send("Processed with errors");
    }
});
```