Skip to main content

The Source of Truth

Important: Do not use the frontend onSuccess callback as the trigger to fulfill orders. Frontend state can be manipulated. Always rely on Webhooks as the final source of truth.

Registering your Webhook

Set your webhookUrl and webhookSecret in the Surge Merchant Dashboard under Settings → Webhooks.

Security

Surge signs every outbound webhook with an HMAC-SHA256 signature in the X-Surge-Signature header. Verify this against your webhook_secret before processing any event.
const crypto = require('crypto');

function verifySurgeWebhook(rawBody, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');

  // timingSafeEqual throws if buffers differ in length
  const expBuf = Buffer.from(expected);
  const sigBuf = Buffer.from(signature ?? '');
  return expBuf.length === sigBuf.length && crypto.timingSafeEqual(expBuf, sigBuf);
}
Always verify the signature before acting on webhook data. Never trust the payload without this check.

Outbound Events (Surge → Your Server)

These are the events Surge sends to your registered webhookUrl.

payment.success

Fires when the customer pays the initial deposit and the installment plan is activated.
{
  "eventId": "evt_abc123...",
  "eventType": "payment.success",
  "merchantId": "mer_998877",
  "createdAt": "2026-03-11T12:25:00Z",
  "data": {
    "resourceType": "payment_plan",
    "resourceId": "pp_554433",
    "orderReference": "ORD-9912384",
    "amount": 120000,
    "currency": "NGN"
  }
}

payment.failed

Fires when a deposit or installment charge attempt fails (e.g. insufficient funds, expired card).
{
  "eventId": "evt_fail789...",
  "eventType": "payment.failed",
  "merchantId": "mer_998877",
  "createdAt": "2026-04-11T06:15:00Z",
  "data": {
    "resourceType": "payment_plan",
    "resourceId": "pp_554433",
    "orderReference": "ORD-9912384",
    "reason": "insufficient_funds"
  }
}

payment_plan.installment_paid

Fires each time the Auto-Debit engine successfully collects a scheduled installment. Also emitted as installment.paid — both are identical events with different names for compatibility.
You receive payment.success once when the plan is created, and one payment_plan.installment_paid event for each subsequent installment collected.
{
  "eventId": "evt_def456...",
  "eventType": "payment_plan.installment_paid",
  "merchantId": "mer_998877",
  "createdAt": "2026-04-11T06:15:00Z",
  "data": {
    "resourceType": "installment",
    "resourceId": "inst_778899",
    "paymentPlanId": "pp_554433",
    "customerId": "cus_112233",
    "orderReference": "ORD-9912384",
    "amount": 40000,
    "currency": "NGN",
    "processorReference": "PST_abc123"
  }
}

goods.release

Fires when the merchant’s configured release rule is satisfied — typically after the deposit is paid. This is the trigger to ship physical goods or unlock digital access.
{
  "eventId": "evt_rel321...",
  "eventType": "goods.release",
  "merchantId": "mer_998877",
  "createdAt": "2026-04-11T06:15:00Z",
  "data": {
    "resourceType": "payment_plan",
    "resourceId": "pp_554433",
    "orderReference": "ORD-9912384"
  }
}

debit.scheduled

Fires when an auto-debit job is registered for a future installment.

transfer.success

Fires when a merchant payout (settlement transfer) to your registered bank account completes successfully.

Fulfillment Trigger

When your server receives goods.release or payment.success (and the signature is valid):
  1. Mark the order as Paid in your database.
  2. Trigger the release or shipping of physical or digital goods to the customer.
Return a 200 OK immediately and process fulfillment asynchronously to avoid timeout-based retries from Surge.

Inbound Events (Paystack → Surge)

This section describes Surge’s internal infrastructure — you do not need to configure, call, or handle any of these endpoints. They are listed here for transparency only.
Surge maintains endpoints that receive events from Paystack (the underlying payment processor). These handle charge confirmations, payout confirmations, and payment method revocations on Surge’s behalf.

POST /api/v1/webhooks/paystack

Used internally by Surge to receive Paystack charge and transfer events. Surge validates every inbound Paystack event using HMAC-SHA512 (Paystack’s standard signing algorithm).

POST /api/v1/webhooks/payment-gateway

Used internally by Surge to handle payment method revocation events from Paystack (authorization.revoked, mandate.cancelled, etc.). When Surge receives a revocation, it deactivates the customer’s payment method and recalculates their Surge Score automatically.

Testing Your Webhook

You can send a signed test event to your configured endpoint directly from the API:
POST /api/v1/webhooks/test
Authorization: Bearer <YOUR_JWT_TOKEN>
Content-Type: application/json

{
  "webhook_url": "https://yourstore.com/webhooks/surge",
  "webhook_secret": "whsec_..."
}
Response
{
  "ok": true,
  "delivered": true,
  "status_code": 200,
  "latency_ms": 142
}
If delivery fails, delivered is false and an error field describes the failure.

Replaying Failed Events

If your server was down when a webhook was delivered, you can manually replay any event from the dashboard or via the API:
POST /api/v1/webhooks/events/{event_id}/replay
Authorization: Bearer <YOUR_JWT_TOKEN>
Failed delivery attempts are visible via:
GET /api/v1/webhooks/failed-attempts
Authorization: Bearer <YOUR_JWT_TOKEN>