Stay in sync with real-time event notifications delivered directly to your server.
Super Payments triggers a webhook whenever a significant event occurs within a transaction lifecycle.
- Real-time Updates: Receive immediate notice when a transaction status changes.
- Monitoring: You can verify your configuration and monitor webhook performance through the Webhook Insights section of your business portal, click on your webhook URL to view the events.
Endpoint Configuration
To begin receiving events, you must register your webhook endpoint URL within the Super Payments Portal. This URL is where our server will send POST requests containing event data.
Registering your Endpoint URL
- Access the Portal: Log in to your business portal and navigate to the integrations section.
- Enter your URL: Locate the webhook configuration field and enter the full destination URL of your server (e.g., https://your-api.com/webhooks/super).
- Retrieve your Secret: Once configured, you will see your Confirmation ID (starting with
PWH_). Save this securely, as it is required for signature verification.
Securing your Webhook
To ensure that requests are authentic and originated from Super Payments, every webhook includes a super-signature header.
Headers Format
The super-signature header contains a timestamp and a cryptographic signature, separated by a comma.
super-signature: t:1669219987926,v1:vCcZMqom...base64...
t: — Unix timestamp in milliseconds, generated when the request was signed
v1: — HMAC-SHA256 signature, base64-encoded
Verifying the Signature
To verify the signature, you will need your Confirmation ID, which acts as your webhook secret. This ID starts with PWH_ and is located in the Integrations section of your portal, click on the arrow "View this integration".
- Parse the header. Split on "," to get the timestamp and signature parts, then split each on ":" to extract the values.
- Build the signed message. Concatenate the timestamp and the raw request body, with no separator: "message = timestamp + raw_body"
- Use the raw bytes of the request body - do not re-serialize parsed JSON, as whitespace or key ordering changes will invalidate the signature.
- Compute the expected signature. Generate an HMAC-SHA256 of message using your webhook secret as the key, and base64-encode the result.
- Compare signatures. Use a constant-time comparison (e.g. crypto.timingSafeEqual in Node,
hmac.compare_digestin Python) to avoid timing attacks. Reject the request if they
don't match. - Check the timestamp. Reject requests whose timestamp is more than 5 minutes old to prevent replay attacks.
super-signature: t:1669219987926,v1:bc2719322a26335c486df9dffeb0c555c758b2a7470de6f62421cfb8437e8b5bExample (Node.js)
const crypto = require('crypto');
function verifySuperSignature(rawBody, header, secret) {
const parts = Object.fromEntries(
header.split(',').map((p) => { return p.split(':'); })
);
const timestamp = parts.t;
const signature = parts.v1;
if (Math.abs(Date.now() - Number(timestamp)) > 5 * 60 * 1000) {
return false;
}
const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}${rawBody}`)
.digest('base64');
const expectedBuf = Buffer.from(expected, 'base64');
const actualBuf = Buffer.from(signature, 'base64');
if (expectedBuf.length !== actualBuf.length) {
return false;
}
return crypto.timingSafeEqual(expectedBuf, actualBuf);
}Reliability & Retries
If your server is unavailable or returns an error, Super Payments will automatically retry sending the webhook notification using an exponential backoff strategy.
| Attempt | Resend Interval |
|---|---|
| 1 | 8 seconds |
| 2 | 1 minute |
| 3 | 8 minutes |
| 4 | 1 hour |
| 5 | 8 hours |
| 6 | 12 hours |
Following the sixth attempt, we continue to retry every 12 hours for a maximum of 7 additional attempts .
Event Types
Super Payments sends webhooks for the following event types. Each payload includes an eventType field identifying the event, a unique eventId, an eventDatetime timestamp, and a data object containing the event-specific details.
| Event Type | Triggered When |
|---|---|
payment.success | A payment has been successfully authorised and completed. |
payment.failed | A payment was not completed. Check the status field for the specific reason. |
refund.success | A refund has been successfully processed. |
refund.failed | A refund could not be processed. |
customer.payment_method.requires_action | A saved customer payment method requires further action before it can be used. |
customer.payment_method.enabled | A saved customer payment method has been successfully enabled. |
customer.payment_method.disabled | A saved customer payment method has been disabled. |
payment.success
Triggered when a payment has been successfully authorised and funds captured. The fundingSummary object details how the transaction was funded.
Example Payload - Checkout Session
{
"data": {
"amount": {
"amount": 15000,
"currency": "GBP",
"amountMultiplier": 100
},
"source": "checkoutSession",
"status": "PaymentSuccess",
"brandId": "123-123-123-123",
"paymentId": "123-123-123-123",
"fundingSummary": {
"superFundedAmount": {
"amount": 0,
"currency": "GBP",
"amountMultiplier": 100
},
"customerFundedAmount": {
"amount": 15000,
"currency": "GBP",
"amountMultiplier": 100
},
"merchantFundedAmount": {
"amount": 0,
"currency": "GBP",
"amountMultiplier": 100
},
"cashPayableToMerchant": {
"amount": 15000,
"currency": "GBP",
"amountMultiplier": 100
}
},
"surchargeAmount": {
"amount": 600,
"currency": "GBP",
"amountMultiplier": 100
},
"paymentReference": "PAYMENT123",
"externalReference": "TestOrder123",
"paymentInitiatorId": "123-123-123-123"
},
"eventId": "evt_test_123",
"eventType": "payment.success",
"eventDatetime": "2026-03-15T11:09:09.000Z"
}Example Payload - Payment Link
{
"data": {
"amount": {
"amount": 5000,
"currency": "GBP",
"amountMultiplier": 100
},
"source": "paymentLink",
"status": "PaymentSuccess",
"brandId": "123-123-123-123",
"paymentId": "123-123-123-123",
"fundingSummary": {
"superFundedAmount": {
"amount": 0,
"currency": "GBP",
"amountMultiplier": 100
},
"customerFundedAmount": {
"amount": 5000,
"currency": "GBP",
"amountMultiplier": 100
},
"merchantFundedAmount": {
"amount": 0,
"currency": "GBP",
"amountMultiplier": 100
},
"cashPayableToMerchant": {
"amount": 5000,
"currency": "GBP",
"amountMultiplier": 100
}
},
"surchargeAmount": {
"amount": 600,
"currency": "GBP",
"amountMultiplier": 100
},
"paymentReference": "PAYMENT123",
"externalReference": "TestOrder123",
"paymentInitiatorId": "123-123-123-123"
},
"eventId": "evt_test_123",
"eventType": "payment.success",
"eventDatetime": "2026-03-15T10:20:28.000Z"
}payment.failed
Triggered when a payment was not completed. The status field in the data object indicates the specific reason:
| Status | Description |
|---|---|
PaymentFailed | The transaction was declined or could not be processed by the provider. |
PaymentCancelled | The customer manually cancelled the transaction before completion. |
PaymentAbandoned | The customer closed the checkout session without taking action. |
Note:
fundingSummarywill benullfor all failed payment events.
Example Payload - PaymentCancelled
{
"data": {
"amount": {
"amount": 15000,
"currency": "GBP",
"amountMultiplier": 100
},
"source": "checkoutSession",
"status": "PaymentCancelled",
"brandId": "123-123-123-123",
"paymentId": "123-123-123-123",
"fundingSummary": null,
"paymentReference": "PAYMENT123",
"externalReference": "TestOrder123",
"paymentInitiatorId": "123-123-123-123"
},
"eventId": "evt_test_123",
"eventType": "payment.failed",
"eventDatetime": "2026-03-15T08:54:24.000Z"
}Example Payload - PaymentAbandoned
{
"data": {
"amount": {
"amount": 15000,
"currency": "GBP",
"amountMultiplier": 100
},
"source": "checkoutSession",
"status": "PaymentAbandoned",
"brandId": "123-123-123-123",
"paymentId": "123-123-123-123",
"fundingSummary": null,
"paymentReference": "PAYMENT123",
"externalReference": "TestOrder123",
"paymentInitiatorId": "123-123-123-123"
},
"eventId": "evt_test_123",
"eventType": "payment.failed",
"eventDatetime": "2026-03-14T13:32:28.000Z"
}refund.success
Triggered when a refund has been successfully processed against an original transaction.
Example Payload
{
"data": {
"amount": {
"amount": 15000,
"currency": "GBP",
"amountMultiplier": 100
},
"status": "RefundSuccess",
"brandId": "123-123-123-123",
"refundId": "123-123-123-123",
"refundReference": "REFUND123",
"refundComponents": {
"superAmount": {
"amount": 0,
"currency": "GBP",
"amountMultiplier": 100
},
"customerAmount": {
"amount": 15000,
"currency": "GBP",
"amountMultiplier": 100
},
"merchantAmount": {
"amount": 0,
"currency": "GBP",
"amountMultiplier": 100
}
},
"externalReference": "TestOrder123",
"originatingPaymentId": "123-123-123-123",
"originatingPaymentSource": "checkoutSession",
"originatingPaymentExternalReference": "TestOrder123"
},
"eventId": "evt_test_123",
"eventType": "refund.success",
"eventDatetime": "2026-03-15T10:46:27.000Z"
}refund.failed
Triggered when a refund could not be processed against an original transaction.
Example Payload
{
"data": {
"amount": {
"amount": 15000,
"currency": "GBP",
"amountMultiplier": 100
},
"status": "RefundFailed",
"brandId": "123-123-123-123",
"refundId": "123-123-123-123",
"refundReference": "REFUND123",
"refundComponents": {
"superAmount": {
"amount": 0,
"currency": "GBP",
"amountMultiplier": 100
},
"customerAmount": {
"amount": 15000,
"currency": "GBP",
"amountMultiplier": 100
},
"merchantAmount": {
"amount": 0,
"currency": "GBP",
"amountMultiplier": 100
}
},
"externalReference": "TestOrder123",
"originatingPaymentId": "123-123-123-123",
"originatingPaymentSource": "checkoutSession",
"originatingPaymentExternalReference": "TestOrder123"
},
"eventId": "evt_test_123",
"eventType": "refund.failed",
"eventDatetime": "2026-03-15T10:46:27.000Z"
}customer.payment_method.requires_action
Triggered when a saved customer payment method requires further action before it can be used for off-session payments. You should prompt the customer to re-authenticate or update their payment details.
Example Payload
{
"data": {
"type": "CARD",
"usage": "OFF_SESSION",
"status": "REQUIRES_ACTION",
"customerId": "cus_123",
"merchantId": "123-123-123-123",
"paymentMethodId": "pm_123"
},
"eventId": "evt_test_123",
"eventType": "customer.payment_method.requires_action",
"eventDatetime": "2026-03-12T16:21:16.000Z"
}customer.payment_method.enabled
Triggered when a saved customer payment method has been successfully enabled and is ready to be used for off-session payments.
Example Payload
{
"data": {
"type": "CARD",
"usage": "OFF_SESSION",
"status": "ENABLED",
"customerId": "cus_123",
"merchantId": "123-123-123-123",
"paymentMethodId": "pm_123"
},
"eventId": "evt_test_123",
"eventType": "customer.payment_method.enabled",
"eventDatetime": "2026-03-10T13:12:40.000Z"
}customer.payment_method.disabled
Triggered when a saved customer payment method has been disabled and can no longer be used for off-session payments.
Example Payload
{
"data": {
"type": "CARD",
"usage": "OFF_SESSION",
"status": "DISABLED",
"customerId": "cus_123",
"merchantId": "123-123-123-123",
"paymentMethodId": "pm_123"
},
"eventId": "evt_test_123",
"eventType": "customer.payment_method.disabled",
"eventDatetime": "2026-03-10T13:16:06.000Z"
}Payment Identification
Every webhook payload includes a paymentId that uniquely identifies the payment transaction. This is the same value as the paymentIntentId returned in the /proceed endpoint and the id returned in the /payments endpoint response.
| Source | Field | Description |
|---|---|---|
/proceed | /payments response | paymentIntentId | id | Unique identifier assigned to the payment transaction |
| Webhook payload | paymentId | The same identifier, referencing the transaction in status updates |
Use this shared identifier to correlate webhook events with the payment transactions initiated from your server.
Matching Webhooks to Orders
The externalReference field in the webhook payload is a pass-through value set by the merchant at payment creation. Super Payments does not use this field to deduplicate or relate transactions.
Because multiple transactions can share the same externalReference, you should use the paymentId as the primary identifier when processing webhook events.
Recommended approach:
- When calling
/proceed|/payments, store the returnedpaymentIntentId|idagainst your order. - If the customer retries the payment, call the relavent endpoint again and update the stored ID with the new value.
- When a webhook is received, validate that the incoming
paymentIdmatches thepaymentIntentId|idcurrently associated with your order. - Discard any webhook where the
transactionIddoes not match.
ON /proceed response:
order.paymentIntentId = response.paymentIntentId
SAVE order
ON webhook received:
order = FIND order BY webhook.externalReference
IF webhook.paymentId != order.paymentIntentId:
LOG "Ignoring webhook for outdated transaction"
RETURN 200 OK
ELSE:
UPDATE order status
RETURN 200 OKSupported Payment Methods
Webhooks are triggered for the following:
- Payments: created through the embedded checkout components, the Payment Intents API, and the Payment Links API.
- Refunds: processed through the Refunds API.
- Saved Cards: state changes to customer payment methods (enabled, disabled, or requires action) for merchants using off-session payments.
Best Practices
Return a 2xx response
Your endpoint must quickly return a successful status code (2xx) prior to executing any complex logic that could cause a timeout. For example, you should return a 200 response immediately upon receiving the event, before performing tasks like updating your database or triggering order fulfillments. This prevents Super from marking the attempt as a failure and initiating retries.
