Managing Webhooks

Webhooks allow the Prestmit API to send real-time notifications to your application when specific events occur.

๐Ÿ”„ How Webhooks Work

Webhooks operate on a simple principle:

  1. Registration: You register a URL endpoint with Prestmit to receive notifications. You can do that here: https://console.prestmit.io/developers
  2. Event Occurs: A specific event happens in the Prestmit system (e.g., withdrawal status change)
  3. Notification: Prestmit sends an HTTP POST request to your registered URL with event data
  4. Verification: Your server verifies the webhook signature to ensure authenticity
  5. Processing: Your application processes the event data and takes appropriate actions

๐Ÿ› ๏ธ Setting Up Webhooks


Step 1: Create a Webhook Endpoint

Set up an endpoint in your application to receive webhook notifications. This endpoint should:

  • Accept HTTP POST requests
  • Return a 200 OK response to acknowledge receipt

Note: Only HTTPS URLs are allowed for security reasons


Step 2: Register Your Webhook URL

You can now register your webhook URL directly through the Prestmit Console:

  • Go to Console > Webhooks Management
  • Click on the "Add New Webhook" button and enter your webhook URL in the "Webhook URL" field
  • Check the boxes for the specific events you want to receive notifications for
  • Select your preferred API Access Key from the dropdown menu. You can only attach one webhook URL to an API key.
  • Once submitted, a Webhook Verification Signing Key will be generated for you
  • Store this secret key securely in your environment variables, as you'll need it to verify incoming webhooks
  • Your webhook is now registered and will begin receiving events
  • You can manage your webhooks (add, edit, or delete) from the same dashboard

๐Ÿ“Š Common Webhook Events and Payloads


Available Webhook Events

EventDescription
giftcard-trade.sell.approvedGift card sell trade has been approved
giftcard-trade.sell.rejectedGift card sell trade has been rejected
giftcard-trade.buy.approvedGift card buy trade has been approved
giftcard-trade.buy.rejectedGift card buy trade has been rejected
withdrawal-request.approvedNaira withdrawal request has been approved
withdrawal-request.rejectedNaira withdrawal request has been rejected
withdrawal-request.cedis.approvedCedis withdrawal request has been approved
withdrawal-request.cedis.rejectedCedis withdrawal request has been rejected

Sample Webhook Payload

{
  "data": {
    "id": 89,
    "amount": "1000.00",
    "status": "REJECTED",
    "wallet": "NAIRA",
    "createdAt": "2025-09-08T16:07:24+00:00",
    "balanceAfter": null,
    "balanceBefore": null,
    "transactionSource": "partners_api",
    "partnersApiIdentifier": "153fbede-4f1b-4ac9-b648-982377c698a2",
    "rejectReason": "We cannot withdraw to your bank at the moment. Please try again later"
  },
  "event": "withdrawal-request.rejected",
  "accountID": 58
}

๐Ÿ” Verifying Webhook Signatures in Your Server

Every webhook request from Prestmit includes a signature in the header:

x-prestmit-signature: <signature>

This signature is a SHA-256 HMAC hash, generated using your Webhook Secret Key and the raw request body. Your system should verify this signature before processing the webhook to ensure that the request truly came from Prestmit and not from an unknown or malicious source.


How Signature Verification Works

  1. Receive the webhook and extract:

    • The raw JSON body (payload)
    • The value of the x-prestmit-signature header
  2. Generate your own hash using the same algorithm (sha256) and your webhook secret provided on the Prestmit console.

  3. Compare your generated hash to the signature from the header.

    • If they match โ†’ the webhook is authentic.
    • If they donโ€™t โ†’ reject the request and return a 401 Unauthorized or 403 Forbidden response.

Example Implementation

import crypto from "crypto";

/**
 * Verifies the signature of an incoming webhook request
 * @param {Object} payload - The webhook payload (request body)
 * @param {string} signature - The signature from the x-prestmit-signature header
 * @param {string} secret - Your webhook secret key
 * @returns {boolean} - Whether the signature is valid
 */
function verifyWebhookSignature(payload, signature, secret) {
  const expectedSignature = crypto
    .createHmac("sha256", secret)
    .update(JSON.stringify(payload))
    .digest("base64");

  return signature === expectedSignature;

}

Checklist for Secure Verification

  • Always verify the raw body, not the parsed one
  • Store your webhook secret securely in environment variables
  • Log invalid attempts for security monitoring
  • Return 401 for invalid signatures and stop further processing
  • โš ๏ธ Important: When verifying signatures in Node.js/Express, capture the raw request body before itโ€™s parsed.

๐Ÿงช Testing Webhooks

Using the Test Script

The Prestmit API sample includes a test script for simulating webhook events:

_testWithdrawalWebhook.js_

#!/usr/bin/env node

/**
 * Script to test the withdrawal webhook with proper signature
 * Usage: node scripts/testWithdrawalWebhook.js
 */

const crypto = require("crypto");
const axios = require("axios");
const config = require("../src/config/config");

// Configuration
const webhookConfig = {
  webhookUrl: "http://localhost:3000/api/wallet/webhook",
  // This should match the secret in your server
  webhookSecret: config.prestmitAPI.webhookSecret,
};

// Sample webhook data for a rejected withdrawal
const webhookData = {
  data: {
    id: 89,
    amount: "1000.00",
    status: "REJECTED",
    wallet: "NAIRA",
    createdAt: "2025-09-08T16:07:24+00:00",
    balanceAfter: null,
    rejectReason:
      "We cannot withdraw to your bank at the moment. Please try again later",
    balanceBefore: null,
    transactionSource: "partners_api",
    partnersApiIdentifier: "153fbede-4f1b-4ac9-b648-982377c698a2",
  },
  event: "withdrawal-request.rejected",
  accountID: 58,
};

// Function to send webhook data
async function sendWebhook() {
  try {
    // Convert payload to string - IMPORTANT: use the same JSON.stringify as the server will
    const payloadString = JSON.stringify(webhookData);

    // Generate signature
    const signature = crypto
      .createHmac("sha256", webhookConfig.webhookSecret)
      .update(payloadString)
      .digest("base64");

    const response = await axios.post(webhookConfig.webhookUrl, webhookData, {
      headers: {
        "Content-Type": "application/json",
        "x-prestmit-signature": signature,
      },
    });

    return response.data;
  } catch (error) {
    console.error("Error sending webhook:");
    if (error.response) {
      // The request was made and the server responded with a status code
      // that falls out of the range of 2xx
      console.error("Response status:", {
        status: error.response.status,
        data: error.response.data,
      });
    } else if (error.request) {
      // The request was made but no response was received
      console.error("No response received:", error.request);
    } else {
      // Something happened in setting up the request that triggered an Error
      console.error("Error message:", error.message);
    }
    throw error;
  }
}

// Execute the webhook test
sendWebhook()
  .then(() => console.log("Webhook test completed"))
  .catch(() => console.log("Webhook test failed"));

To run the test:

node scripts/testWithdrawalWebhook.js

Using Webhook Testing Tools

You can also use third-party tools to test your webhook implementation:

  • Webhook.site: Create a temporary endpoint to inspect webhook payloads
  • ngrok: Create a secure tunnel to your local development environment
  • Postman: Send custom webhook requests with proper headers

โš ๏ธ Common Issues and Troubleshooting


1. Signature Verification Failures

Problem: Webhook signature verification fails consistently.

Solutions:

  • Ensure you're using the correct webhook secret key
  • Check that you're using the exact same JSON serialization method as Prestmit
  • Verify that no middleware is modifying the request body before verification

2. Missing Webhook Events

Problem: Some webhook events are not being received.

Solutions:

  • Confirm your webhook URL is publicly accessible
  • Check your server logs for any errors in processing
  • Verify on Prestmit dashboard that you're subscribed to all relevant events
  • Reach out to our team for support

3. Duplicate Webhook Events

Problem: The same webhook event is processed multiple times.

Solutions:

  • Implement idempotency by tracking processed webhook IDs
  • Use database transactions to ensure atomic operations
  • Add a deduplication mechanism based on event IDs
/**
 * Processes a webhook event with idempotency to prevent duplicate processing
 * @param {string} event - The webhook event type
 * @param {Object} data - The webhook payload data
 */
async function processWebhook(event, data) {
  // Check if we've already processed this webhook
  const existingWebhook = await ProcessedWebhook.findOne({
    eventId: data.id,
    eventType: event
  });
  
  if (existingWebhook) {
    console.log(`Webhook ${event} for ID ${data.id} already processed, skipping`);
    return;
  }
  
  // Process the webhook
  // ... your webhook processing logic here ...
  
  // Record that we've processed this webhook
  await ProcessedWebhook.create({
    eventId: data.id,
    eventType: event,
    processedAt: new Date()
  });
}


๐Ÿ†˜ Need Help?

If you encounter any issues with webhook setup or processing:

  • Check the Prestmit API documentation for webhook specifications
  • Review your server logs for detailed error information
  • Contact Prestmit support atย [email protected] or via the official channel provided to you.

Whatโ€™s Next

Next... Let's look at IP whitelisting and its benefits.