All products
Webhooks

Signed event delivery you can build on.

Most payment platforms treat webhooks as an afterthought. MerasPay treats them as a first-class product: HMAC-SHA256 signatures, exponential retry up to 72 hours, full delivery history, and an inspector that lets your engineers replay events without leaving the dashboard.

At a glance

Signature
HMAC-SHA256
Retry attempts
Up to 8
Retry window
72h
Event types
60+

Everything you would expect from production-grade webhooks

The defaults are right for a serious integration. The escape hatches are there when you need them.

HMAC-SHA256 signing

Every delivery is signed over {timestamp}.{event_id}.{body} with a per-endpoint secret. Reject deliveries with mismatched signatures in five lines of code on your server.

Exponential retry

Failed deliveries retry on a schedule: 1m, 5m, 15m, 1h, 6h, 24h, 48h, 72h. Configurable per endpoint. After the final attempt the event is parked for manual replay.

Event subscription filters

Subscribe each endpoint to a subset of event types — payment_intent.*, payout.*, dispute.*, subscription.* — instead of firehosing every event to every endpoint.

Idempotent receivers

Every event carries an event_id and a timestamp. Repeat deliveries are common (retries) — your handler should be idempotent on event_id, and we make that easy with stable ids.

Delivery history + replay

Every delivery attempt is stored with HTTP status, response body, latency, and signature. Replay any past event from the dashboard or via /v1/events/:id/replay.

Local testing

Use the sandbox simulator + ngrok to receive signed deliveries on localhost. Or use the webhook inspector to watch deliveries land without leaving the dashboard.

The lifecycle

Four steps. The verification snippet on your end is shorter than this paragraph.

  1. 1

    Register an endpoint

    POST /v1/webhook_endpoints with the URL and the event types you care about. You receive an endpoint secret — store it in your secret manager.

  2. 2

    We sign every delivery

    Each POST carries MerasPay-Signature: t=<unix>,v1=<hex_hmac>. The signature payload is {t}.{event.id}.{raw_body}.

  3. 3

    You verify in five lines

    Recompute the HMAC with your endpoint secret and compare in constant time. Reject anything that does not match. Reject anything older than five minutes.

  4. 4

    You acknowledge with 2xx

    Any 2xx response within 30 seconds counts as success. Anything else triggers the next retry slot. The delivery row records every attempt for the audit trail.

Register an endpoint

Per-endpoint secret. Per-endpoint event filter. Per-endpoint retry policy. Three things most platforms make you bolt on as middleware are built in.

bash
# Register an endpoint that wants payment + dispute events only.
$ curl -X POST https://api.meraspay.io/v1/webhook_endpoints \
  -u sk_live_xxx: \
  -d url=https://ops.example.io/webhooks/meraspay \
  -d "enabled_events[]=payment_intent.*" \
  -d "enabled_events[]=dispute.*"

{
  "id": "wh_01HZF...",
  "url": "https://ops.example.io/webhooks/meraspay",
  "secret": "whsec_4yV2…",      # store this; will not be returned again
  "enabled_events": ["payment_intent.*", "dispute.*"]
}

Verify a delivery

Constant-time HMAC compare + age check. Five-minute window blocks replay attacks.

typescript
// Verify a MerasPay webhook signature in Node.
import crypto from 'crypto';

export function verify(req, secret) {
  const header = req.headers['meraspay-signature'];
  const [tPart, sigPart] = header.split(',');
  const t = tPart.replace('t=', '');
  const sig = sigPart.replace('v1=', '');

  // Reject anything older than 5 minutes to block replay attacks.
  const age = Date.now() / 1000 - Number(t);
  if (age > 300) throw new Error('webhook: stale');

  const payload = `${t}.${JSON.parse(req.rawBody).id}.${req.rawBody}`;
  const expected = crypto.createHmac('sha256', secret).update(payload).digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
    throw new Error('webhook: signature mismatch');
  }
}

Sample of the event catalogue

The full list is in the OpenAPI spec. Below are the events most integrations subscribe to first.

TypeFires when
payment_intent.createdA PaymentIntent was created and is pending or requires_action.
payment_intent.requires_actionCustomer-side action needed (OTP, redirect, etc.) before the intent can confirm.
payment_intent.succeededThe payment was confirmed and ledger entries posted.
payment_intent.payment_failedThe rail or customer declined; the intent is in a terminal failed state.
payout.createdA payout was queued.
payout.paidFunds reached the recipient successfully.
payout.failedThe payout failed and funds returned to the merchant balance.
refund.created / refund.updatedA refund moved through its state machine.
dispute.created / dispute.updated / dispute.closedA dispute opened, evidence was submitted, or it resolved.
invoice.payment_succeeded / invoice.payment_failedRecurring billing collection results.
checkout_session.completed / checkout_session.expiredHosted Checkout session lifecycle.
remittance_order.*Origination or termination lifecycle transitions.

Frequently asked

What if my endpoint is down for hours?

Deliveries retry on an exponential schedule for up to 72 hours. After that the delivery is parked but the event remains stored — replay any time from the dashboard or via /v1/events/:id/replay.

How do I rotate an endpoint secret?

POST /v1/webhook_endpoints/:id/rotate. We surface both old and new secrets for a configurable overlap window (default 24 hours) so you can roll secrets without dropping deliveries.

Do you deliver events in order?

Best effort. Most events arrive in order, but retries break ordering. Design your handler to be order-independent on a single resource — re-read the resource from /v1 if you need the latest state.

How do I test webhooks locally?

Two options. Register an ngrok URL as the endpoint URL. Or use the webhook inspector in the merchant portal — it captures every delivery for any endpoint and lets you replay events to localhost via the CLI.

Can I get inbound webhooks from MerasPay (e.g. when a customer pays)?

Yes — that is exactly what this product is. The events listed above include payment_intent.succeeded, checkout_session.completed, qr_payment.succeeded, etc. Subscribe an endpoint to whichever events your downstream systems need.

Ready to integrate?

Get a sandbox account in minutes. Production goes live after a brief KYB review.