Skip to main content
Monigo sends webhooks to your server when important events occur — invoice paid, payout completed, customer wallet topped up, usage period stats, and more.

Setting up a webhook endpoint

  1. Go to Dashboard → Webhooks → Add Endpoint
  2. Enter your HTTPS URL
  3. Select the event types you want to receive
  4. Copy the signing secret

Verifying webhook signatures

Every Monigo webhook includes a X-Monigo-Signature header. Verify it before processing the request.
import { createHmac, timingSafeEqual } from 'crypto';

function verifyWebhook(payload: string, signature: string, secret: string): boolean {
  const expected = createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  return timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(`sha256=${expected}`)
  );
}

// In your handler:
app.post('/webhooks/monigo', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['x-monigo-signature'] as string;
  if (!verifyWebhook(req.body.toString(), sig, process.env.MONIGO_WEBHOOK_SECRET!)) {
    return res.status(401).send('Invalid signature');
  }
  const event = JSON.parse(req.body);
  // handle event...
  res.status(200).send('ok');
});

Event types

Payment

EventDescription
payment.successA payment transaction succeeded
payment.failedA payment transaction failed
payout.successA payout transfer completed successfully
payout.failedA payout transfer failed
payout.reversedA payout transfer was reversed

Invoice

EventDescription
invoice.finalizedAn invoice was finalized (moved from draft to finalized)
invoice.paidAn invoice was marked as paid after a successful payment
invoice.voidedAn invoice was voided

Customer

EventDescription
customer.wallet.topped_upA customer’s wallet was credited
customer.payment_method.expiredA customer’s payment method has expired
customer.payment_method.expiring_soonA customer’s payment method is expiring within 30 days

Usage — org level

Fired once per organization per period (daily at 08:00 UTC, weekly on Mondays, monthly on the 1st).
EventDescription
usage.dailyYesterday’s event ingestion stats for the org
usage.weeklyLast 7 days’ event ingestion stats for the org
usage.monthlyLast calendar month’s event ingestion stats for the org

Usage — per customer

Fired once per customer that had non-zero activity in the period. Sent at the same time as the org-level events.
EventDescription
customer.usage.dailyYesterday’s event count for a single customer
customer.usage.weeklyLast 7 days’ event count for a single customer
customer.usage.monthlyLast calendar month’s event count for a single customer

Subscription

EventDescription
subscription.suspendedA subscription was suspended after dunning retries were exhausted
subscription.usage_cap_reachedA customer’s usage crossed the included-units threshold on an overage or cap-priced plan

Event payload reference

All webhooks are delivered as POST requests with Content-Type: application/json. The body is a JSON object whose shape depends on the event type.

payment.success / payment.failed

The payload mirrors the raw event received from your payment provider (Paystack, Flutterwave, or Monnify).
{
  "event": "charge.success",
  "data": {
    "reference": "pay_abc123",
    "amount": 500000,
    "currency": "NGN"
  }
}

payout.success / payout.failed / payout.reversed

The payload mirrors the raw payout event from your payment provider.

invoice.finalized

{
  "invoice_id": "01924f1e-...",
  "customer_id": "01924f1e-...",
  "subscription_id": "01924f1e-...",
  "amount": "5000.000000",
  "currency": "NGN",
  "period_start": "2026-02-01T00:00:00Z",
  "period_end": "2026-03-01T00:00:00Z"
}

invoice.paid

{
  "invoice_id": "01924f1e-...",
  "customer_id": "01924f1e-...",
  "subscription_id": "01924f1e-...",
  "amount": "5000.000000",
  "currency": "NGN",
  "period_start": "2026-02-01T00:00:00Z",
  "period_end": "2026-03-01T00:00:00Z",
  "paid_at": "2026-02-14T10:32:00Z"
}

invoice.voided

{
  "invoice_id": "01924f1e-...",
  "customer_id": "01924f1e-...",
  "subscription_id": "01924f1e-...",
  "amount": "5000.000000",
  "currency": "NGN",
  "period_start": "2026-02-01T00:00:00Z",
  "period_end": "2026-03-01T00:00:00Z"
}

customer.wallet.topped_up

{
  "customer_id": "01924f1e-...",
  "wallet_id": "01924f1e-...",
  "amount": "10000.000000",
  "currency": "NGN",
  "entry_type": "deposit",
  "balance_after": "25000.000000"
}

customer.payment_method.expired / customer.payment_method.expiring_soon

{
  "customer_id": "01924f1e-...",
  "payment_method_id": "01924f1e-...",
  "last4": "4242",
  "exp_month": 2,
  "exp_year": 2026,
  "card_type": "visa"
}

usage.daily

{
  "period": "Feb 13, 2026",
  "events_yesterday": 14823,
  "events_mtd": 198450,
  "active_customers": 312
}

usage.weekly

{
  "period": "2026-02-07/2026-02-14",
  "events_count": 98210,
  "active_customers": 540
}

usage.monthly

{
  "period": "2026-01",
  "events_count": 1842300,
  "active_customers": 1204
}

customer.usage.daily / customer.usage.weekly / customer.usage.monthly

{
  "customer_id": "01924f1e-...",
  "period": "Feb 13, 2026",
  "events_count": 423
}

subscription.suspended

{
  "customer_id": "01924f1e-...",
  "customer_name": "Acme Corp",
  "subscription_id": "01924f1e-...",
  "invoice_id": "01924f1e-...",
  "amount": "5000.000000",
  "currency": "NGN"
}

subscription.usage_cap_reached

Fired the first time a customer’s usage crosses the included_units threshold on an overage or cap-priced plan within the current billing period. Fires at most once per subscription per billing period.
{
  "subscription_id": "01924f1e-...",
  "customer_id": "01924f1e-...",
  "plan_id": "01924f1e-...",
  "metric_id": "01924f1e-...",
  "pricing_model": "overage",
  "included_units": 10000,
  "current_usage": 10247,
  "period_start": "2026-02-01T00:00:00Z",
  "period_end": "2026-03-01T00:00:00Z"
}

Retries

If your endpoint returns a non-2xx status, Monigo retries up to 5 times with exponential backoff:
AttemptDelay
1st retry30 seconds
2nd retry5 minutes
3rd retry30 minutes
4th retry2 hours
5th retry8 hours
After 5 failed attempts, the delivery is marked as failed and no further retries occur. You can manually retry from the dashboard.

Testing webhooks locally

use a tunnelling tool like ngrok and register the public URL in your dashboard.