payment-gateway.app Docs
API Reference

Webhooks & IPN

Inbound provider webhooks and outbound IPN configuration for real-time payment event notifications.

Webhooks & IPN

The Payment Gateway handles webhooks at two levels:

  1. Inbound webhooks — from payment providers (Stripe, GoCardless) to the Main Backend. These update transaction state automatically.
  2. Outbound IPN (Instant Payment Notifications) — from the Main Backend to your server when a transaction’s status changes.

Provider callbacks and IPN delivery are not served by the Admin Backend. Configure provider webhook URLs on your Main Backend host ({$MAIN_BACKEND_DOMAIN} — documentation uses webhook.yourcompany.com as an example; distinct from api.* / {$ADMIN_BACKEND_DOMAIN}). That service exposes /hooks/... at the server root and checkout runtime under /api/v1/....


Inbound Provider Webhooks

Endpoint

On your Main Backend base host (no /api/v1 prefix):

POST https://webhook.yourcompany.com/hooks/{type}/{providerId}

Hosted equivalent: POST https://webhook.payment-gateway.app/hooks/{type}/{providerId}Hostnames & DNS conventions.

ParameterValuesDescription
typestripe, gocardlessProvider type
providerIdMongoDB ObjectIdThe provider record ID (from Admin Panel)

Configure this URL in your provider dashboard as the webhook destination. The gateway verifies the request signature using the Webhook Secret stored on the provider record.

Stripe Events Handled

Stripe EventEffect on Transaction
payment_intent.succeededsucceeded
payment_intent.payment_failedfailed
payment_intent.canceledcancelled
charge.refundedRecords refund, triggers credit note if invoice exists
charge.dispute.createdOpens chargeback record
charge.dispute.closedCloses chargeback with outcome

GoCardless Events Handled

GoCardless EventEffect on Transaction
payments.confirmedsucceeded
payments.failedfailed
payments.cancelledcancelled
mandates.cancelledMarks mandate as cancelled
mandates.failedMarks mandate as failed
refunds.createdRecords refund

Signature Verification

Stripe: The gateway validates the Stripe-Signature header using your configured Stripe Webhook Secret (whsec_...).

GoCardless: The gateway validates the Webhook-Signature header using your GoCardless Webhook Secret.

If signature verification fails or the payload is invalid, the gateway returns 400 and logs the attempt. Do not disable webhook verification in production.


Outbound IPN (to Your Application)

When you create a checkout (Admin Checkouts → Create or POST /v1/checkouts/{siteId}/create / POST /api/v1/checkouts/{siteId}/create on the Admin API host), you can set ipnUrl to the HTTPS endpoint on your infrastructure that should receive IPN posts for transactions created from that checkout. The URL is stored on the transaction (see Transactions → [transaction] → IPN Information in the admin UI for delivery attempts and last HTTP status).

Signing uses the Webhook Signing Secret from the Site that owns the checkout: Sites → Add New Site or Sites → Edit SiteWebhook Signing Secret (often whsec_…). Regenerate it with Regenerate if it is compromised; then update every integration that verifies IPN signatures.

Configuring IPN

  1. Copy Webhook Signing Secret from Sites → Edit Site (or create a site and note the secret).
  2. When creating each checkout session, set ipnUrl to your listener URL (http is allowed for local development; use HTTPS in production).
  3. Verify incoming posts using the headers and algorithm below (examples also appear under Payment Session API → Webhook / IPN in the admin UI).

IPN payload (JSON body)

Each POST body is a small JSON object:

{
  "id": "67c8e2f7d6ef0dc8a3fa2011",
  "externalReference": "order_12345",
  "status": 2
}
FieldDescription
idTransaction ID (MongoDB ObjectId hex string).
externalReferenceOptional merchant reference from the checkout (omitted when empty).
statusInteger transaction status code (same numeric codes used internally by the gateway).

Interpret status using your server-side mapping or the Transactions documentation — outbound IPN does not use string event names like transaction.succeeded.

IPN request headers

HeaderDescription
X-Signature-TimestampUnix timestamp (seconds, UTC) when the gateway signed the request.
X-Signature-HMAC-SHA256Hex-encoded HMAC-SHA256 of the string {timestamp}.{rawBody} using your site's Webhook Signing Secret (whsec_…).

rawBody must be the exact JSON bytes as received (no re-formatting) so the signature matches.

Official WooCommerce and aMember Pro plugins implement this verification for you.

IPN signature verification

Verification example (Go):

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "fmt"
)

func verifyIPN(rawBody []byte, timestamp, signatureHex, secret string) bool {
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write([]byte(fmt.Sprintf("%s.%s", timestamp, string(rawBody))))
    expected := hex.EncodeToString(mac.Sum(nil))
    return hmac.Equal([]byte(expected), []byte(signatureHex))
}

Verification example (Node.js):

const crypto = require("crypto");

function verifyIPN(rawBody, timestamp, signatureHex, secret) {
  const payload = `${timestamp}.${rawBody}`;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(payload)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signatureHex),
  );
}

[!CAUTION] Always verify both the timestamp and the HMAC before trusting the payload. Reject stale timestamps if you need replay protection (the official plugins apply their own windows). Failing to verify allows attackers to forge payment confirmations.

IPN retry behavior

The sender treats delivery as successful only when your endpoint returns HTTP 200. Any other status code, a network error, or a timeout causes a retry with exponential backoff, up to three POST attempts in total. Use Transactions → [transaction] → IPN Information in the Admin Panel to inspect attempts and the last HTTP status.

Responding to IPN

Return 200 OK as soon as you have accepted the payload (verify the signature first). The Main Backend does not treat other 2xx codes (for example 204) as success, so those responses will trigger retries. If work is slow, return 200 immediately and process the event asynchronously.

HTTP/1.1 200 OK

On this page