Merchant webhooks

Receive real-time events with signed HTTP requests.

How webhooks work

How Southbill delivers webhooks, retries, and how to configure endpoints.

How webhooks work

Webhooks let Southbill notify your server whenever something happens on your account — a payment succeeds, an invoice is paid, a dispute is opened.

Configure endpoints

Dashboard → Developers → Webhooks → Add endpoint

  • url — must be HTTPS.
  • description — free-form.
  • enabled_events — pick the events you want. Use * for everything.
  • mode — always live. Southbill issues live keys only.

On save Southbill returns a signing secret (whsec_…) — copy it now, it's shown only once.

Delivery guarantees

  • HTTP POST with Content-Type: application/json.
  • 2xx = success. Any non-2xx (or timeout > 20 s) triggers exponential backoff retries for up to 72 hours (roughly 15 attempts).
  • Delivery attempts are visible per-event under the endpoint.
  • Events can arrive out of order. Use data.id + type for idempotency.

Envelope

{
  "id": "evt_01H…",
  "object": "event",
  "type": "checkout.session.completed",
  "created": 1735689600,
  "livemode": true,
  "data": {
    "object": { /* the resource — checkout session, invoice, charge, dispute, payout, … */ }
  }
}

The resource always lives at data.object. See Event reference for the exact shape per event type.

Testing

  • Southbill has no test mode. Validate with a small real-amount payment you refund afterwards.
  • The Developers → Webhooks → Test button sends a synthetic ping.test event.

Verify signatures

HMAC-SHA256 signing scheme — verify before you trust an event.

Verify signatures

Every webhook is signed with your endpoint's whsec_… secret. Verify the signature before parsing the body.

Header

Southbill-Signature: t=1735689600,v1=6a76…f3
  • t — Unix timestamp of when Southbill generated the signature.
  • v1 — HMAC-SHA256 of "{t}.{raw_body}" using your whsec_… secret, hex-encoded.

Steps

  1. Extract t and v1 from the header.
  2. Reject if |now - t| > 300 seconds (replay protection).
  3. Compute expected = HMAC_SHA256(secret, t + "." + rawBody).
  4. Compare expected with v1 in constant time.

Node.js example

import crypto from "node:crypto";

export function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map(p => p.split("=")));
  const t = Number(parts.t);
  if (Math.abs(Date.now()/1000 - t) > 300) throw new Error("expired");
  const expected = crypto.createHmac("sha256", secret)
    .update(`${t}.${rawBody}`).digest("hex");
  const ok = crypto.timingSafeEqual(
    Buffer.from(expected, "hex"),
    Buffer.from(parts.v1, "hex")
  );
  if (!ok) throw new Error("bad_signature");
}

PHP example

[$t, $v1] = [null, null];
foreach (explode(',', $_SERVER['HTTP_SOUTHBILL_SIGNATURE']) as $p) {
  [$k, $v] = explode('=', $p, 2);
  if ($k === 't')  $t  = (int)$v;
  if ($k === 'v1') $v1 = $v;
}
if (abs(time() - $t) > 300) http_response_code(400);
$expected = hash_hmac('sha256', $t.'.'.$raw, $secret);
if (!hash_equals($expected, $v1)) http_response_code(400);

Always use the raw request body — parsing to JSON first will change whitespace and invalidate the signature.

Event reference

Every event Southbill can send, with the payload shape.

Event reference

These are the exact event types Southbill delivers to Merchant API webhook endpoints. Use "*" to subscribe to all of them. Test endpoints only receive livemode: false events; live endpoints only receive live events.

Checkout

  • checkout.session.completed
  • checkout.session.async_payment_pending
  • checkout.session.async_payment_failed
  • checkout.session.canceled
  • checkout.session.refunded
  • checkout.session.expired

Payments

  • payment_intent.succeeded
  • payment_intent.payment_failed
  • payment_intent.requires_action
  • payment_intent.canceled

A declined sandbox card produces a failed payment with failure_code and failure_message, plus payment_intent.payment_failed. The Checkout Session remains retryable with another card.

Invoices

  • invoice.created
  • invoice.sent
  • invoice.paid
  • invoice.partially_paid
  • invoice.payment_succeeded
  • invoice.payment_failed
  • invoice.voided
  • invoice.installment.paid
  • invoice.installment.due
  • invoice.installment.overdue

Store

  • order.paid
  • order.updated
  • order.refunded

Customers

  • customer.created
  • customer.updated
  • customer.deleted

Subscriptions

  • subscription.created — emitted immediately, including while first-payment confirmation is incomplete.
  • subscription.updated
  • subscription.canceled
  • customer.subscription.deleted

Payload

Every event has id, object: "event", type, created, livemode and a resource snapshot under data.object. Deduplicate deliveries by event.id. Events not listed above are not available in the Merchant API endpoint picker. Payouts remain dashboard-managed and are not part of this Merchant API webhook catalog.