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, Mollie, GoCardless, PayPal) 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, mollie, gocardless, paypalProvider type
providerIdMongoDB ObjectIdThe provider record ID (from Admin Panel)

Configure this URL in your provider dashboard as the webhook destination. The gateway verifies provider authenticity with the provider-specific method below and rejects callbacks that do not match the configured provider, organization, or stored payment identifier.

Stripe Events Handled

Stripe EventEffect on Transaction
payment_intent.succeeded-> completed
payment_intent.payment_failed-> failed
charge.refundedRecords refund, triggers credit note if invoice exists
charge.dispute.createdOpens chargeback record
charge.dispute.closedCloses chargeback with outcome

Mollie Events Handled

Mollie webhook bodies are treated as hints. The gateway reads the payment ID from the callback, fetches the payment directly from Mollie with the configured API key, and then validates that the provider, organization, and stored transaction payment ID match before applying any state change. Duplicate event deliveries are guarded with processed-event claims; callbacks with a Mollie event ID are claimed before provider mutation, while form callbacks without an event ID are claimed from the fetched payment state before local mutation.

Mollie Payment StateEffect on Transaction
paid-> succeeded
failed-> failed
canceled-> cancelled
expired-> cancelled
Refund updatesRecords refund, triggers credit note if invoice exists
Chargeback updatesOpens or updates chargeback records

GoCardless Events Handled

GoCardless EventEffect on Transaction
payments.confirmed-> succeeded
payments.failed-> failed
payments.cancelled-> cancelled
mandates.cancelledMarks mandate as cancelled
mandates.failedMarks mandate as failed
refunds.createdRecords refund

PayPal Events Handled

PayPal callbacks are verified with the PayPal transmission headers and the configured PayPal Webhook ID before the gateway applies the event to a transaction. Duplicate deliveries are guarded with processed-event claims before capture, refund, dispute, or status side effects.

PayPal Event FamilyEffect on Transaction
Checkout/order completion events-> succeeded
Payment capture denied/failed events-> failed
Payment capture refunded eventsRecords refund, triggers credit note if invoice exists
Dispute and chargeback eventsOpens or updates chargeback records

Dispute outcomes and customer holds

Provider dispute events share the same final-outcome semantics as manual admin dispute resolution:

  • open and under_review keep the transaction in active dispute state and do not create invoice credit notes.
  • lost and accepted are final merchant-loss outcomes. If the transaction is linked to an invoice, the admin worker creates or links one idempotent chargeback credit note. The gateway can also create a Customer Risk Hold so future checkout attempts are blocked, routed to review, or limited to configured safe provider types such as wire transfer.
  • won restores the payment-side dispute effect and does not create a chargeback credit note or Customer Risk Hold.

This behavior is applied from provider webhooks even when the disputed transaction is not linked to an invoice; in that case the customer hold can still be created, but no invoice credit note is generated.

Signature and Authenticity Verification

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

Mollie: Mollie does not provide a shared-secret signature for these callbacks. The gateway treats the callback payload as an untrusted payment-ID hint, fetches the canonical payment from Mollie, and validates tenant/provider/payment ownership before updating a transaction.

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

PayPal: The gateway validates PayPal transmission headers against the configured PayPal Webhook ID through PayPal's verification API.

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

Webhook authenticity and idempotency are part of the payment-data evidence boundary. See Payment Data Boundary for the customer-facing evidence status and residual responsibilities.


Outbound IPN (to Your Application)

When you create a checkout (Admin Checkouts -> Create or 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 Site -> Webhook 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 when your endpoint returns any HTTP 2xx status code. 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 a 2xx response as soon as you have accepted the payload (verify the signature first). If work is slow, return 200 or 204 immediately and process the event asynchronously.

HTTP/1.1 200 OK

On this page