openapi: 3.2.0
info:
title: Super Payments Webhooks API
description: Super Payments is empowering businesses with free payments, allowing them to offer customers a % Cash Reward, which is automatically deducted from their next purchase when they pay with Super. By rewarding customers in this way, they shop more often and buy more with a business. In addition, Cash Rewards boost customer loyalty and retention, with better conversion rates at typically higher average order values. Cash Rewards also increase adoption of Super Payments as the checkout method, meaning more free payments for your business.
version: '2026-04-01'
contact:
url: https://docs.superpayments.com
license:
name: Super Payments
identifier: https://www.superpayments.com/terms-and-conditions
servers:
- url: https://api.superpayments.com/2026-04-01
description: Live Environment
- url: https://api.test.superpayments.com/2026-04-01
description: Sandbox Environment
tags:
- name: Webhooks
paths: {}
webhooks:
PaymentStatus (integration-specific):
post:
operationId: paymentStatusUpdateWebhook
summary: Payment Status Update Webhook
description: "Configure this webhook against a specific Integration in your Business Portal.\n\n\nHMAC signature verification
\n\n\nEvery webhook request includes a super-signature header so you can verify it came from Super and hasn't been tampered with.\n\n## Header format\n\nThe header contains a timestamp and a signature, separated by a comma:\n\n```super-signature: t:1669219987926,v1:vCcZMqom...base64...```\n\n- `t:` — Unix timestamp in milliseconds, generated when the request was signed\n- `v1:` — HMAC-SHA256 signature, base64-encoded\n\n## Verifying a request\n\n1. Parse the header. Split on \",\" to get the timestamp and signature parts, then split each on \":\" to extract the values.\n2. Build the signed message. Concatenate the timestamp and the raw request body, with no separator: \"message = timestamp + raw_body\"\n2. Use the raw bytes of the request body — do not re-serialize parsed JSON, as whitespace or key ordering changes will invalidate the signature.\n3. Compute the expected signature. Generate an HMAC-SHA256 of message using your webhook secret as the key, and base64-encode the result.\n4. Compare signatures. Use a constant-time comparison (e.g. crypto.timingSafeEqual in Node, hmac.compare_digest in Python) to avoid timing attacks. Reject the request if they\ndon't match.\n5. Check the timestamp. Reject requests whose timestamp is more than 5 minutes old to prevent replay attacks.\n\n## Example (Node.js)\n\n```js\nconst crypto = require('crypto');\n\nfunction verifySuperSignature(rawBody, header, secret) {\n const parts = Object.fromEntries(\n header.split(',').map((p) => { return p.split(':'); })\n );\n const timestamp = parts.t;\n const signature = parts.v1;\n\n if (Math.abs(Date.now() - Number(timestamp)) > 5 * 60 * 1000) {\n return false;\n }\n\n const expected = crypto\n .createHmac('sha256', secret)\n .update(`${timestamp}${rawBody}`)\n .digest('base64');\n\n const expectedBuf = Buffer.from(expected, 'base64');\n const actualBuf = Buffer.from(signature, 'base64');\n\n if (expectedBuf.length !== actualBuf.length) {\n return false;\n }\n\n return crypto.timingSafeEqual(expectedBuf, actualBuf);\n}\n```\n \n"
requestBody:
description: This webhook is called by our payment gateway to inform you of a payment status changes, prior to our payments frontend redirecting the customer back to the given return URL, as specified in the initiate-payment-transaction request body.
required: true
content:
application/json:
schema:
type: object
properties:
eventType:
description: 'This will be set to: PaymentStatus'
type: string
examples:
- PaymentStatus
const: PaymentStatus
transactionId:
description: A unique ID for the payment transaction.
type: string
examples:
- 27b2ae04-cb8e-4bcb-a976-d14a40518db1
transactionReference:
description: An 18 character payment transaction reference allocated by us, used/visible on the associated bank transaction.
type: string
examples:
- CKZ3IS1UB205Y2Y3KU
transactionStatus:
description: 'The payment status of the given payment transaction, specified by the super-transaction_id field above. The payment status can be one of:
- PaymentSuccess: The payment transaction was successful, money has moved from the customer to your holding account.
- PaymentCancelled: The payment transaction was cancelled by the customer, in their banking app.
- PaymentFailed: The payment transaction failed due to a technical error, either on our side or with the customer''s bank
- PaymentDelayed: The payment transaction was not resolved by the bank within 15 seconds (<2% of transactions). Open banking can take up to 4 hours to resolve. The customer has the funds, but this is usually due to additional fraud checks within the banking system.
- PaymentAbandoned: The payment transaction was abandoned by the customer. They closed the browser or just left the page open without explicitly clicking cancel.'
type: string
examples:
- PaymentSuccess
enum:
- PaymentAbandoned
- PaymentCancelled
- PaymentDelayed
- PaymentFailed
- PaymentSuccess
transactionAmount:
description: The payment transaction amount in minor units.
type: integer
examples:
- 10000
externalReference:
description: Your unique reference for the payment transaction e.g. your orderId
type: string
examples:
- order101
required:
- eventType
- transactionId
- transactionReference
- transactionStatus
- transactionAmount
responses:
'200':
description: Return a 200 status to indicate that the data was received successfully, any other response will be deemed a failure and retried
security:
- HMAC: []
tags:
- Webhooks
RefundStatus (integration-specific):
post:
operationId: refundStatusUpdateWebhook
summary: Refund Status Update Webhook
description: "Configure this webhook against a specific Integration in your Business Portal.\n\n\nHMAC signature verification
\n\n\nEvery webhook request includes a super-signature header so you can verify it came from Super and hasn't been tampered with.\n\n## Header format\n\nThe header contains a timestamp and a signature, separated by a comma:\n\n```super-signature: t:1669219987926,v1:vCcZMqom...base64...```\n\n- `t:` — Unix timestamp in milliseconds, generated when the request was signed\n- `v1:` — HMAC-SHA256 signature, base64-encoded\n\n## Verifying a request\n\n1. Parse the header. Split on \",\" to get the timestamp and signature parts, then split each on \":\" to extract the values.\n2. Build the signed message. Concatenate the timestamp and the raw request body, with no separator: \"message = timestamp + raw_body\"\n2. Use the raw bytes of the request body — do not re-serialize parsed JSON, as whitespace or key ordering changes will invalidate the signature.\n3. Compute the expected signature. Generate an HMAC-SHA256 of message using your webhook secret as the key, and base64-encode the result.\n4. Compare signatures. Use a constant-time comparison (e.g. crypto.timingSafeEqual in Node, hmac.compare_digest in Python) to avoid timing attacks. Reject the request if they\ndon't match.\n5. Check the timestamp. Reject requests whose timestamp is more than 5 minutes old to prevent replay attacks.\n\n## Example (Node.js)\n\n```js\nconst crypto = require('crypto');\n\nfunction verifySuperSignature(rawBody, header, secret) {\n const parts = Object.fromEntries(\n header.split(',').map((p) => { return p.split(':'); })\n );\n const timestamp = parts.t;\n const signature = parts.v1;\n\n if (Math.abs(Date.now() - Number(timestamp)) > 5 * 60 * 1000) {\n return false;\n }\n\n const expected = crypto\n .createHmac('sha256', secret)\n .update(`${timestamp}${rawBody}`)\n .digest('base64');\n\n const expectedBuf = Buffer.from(expected, 'base64');\n const actualBuf = Buffer.from(signature, 'base64');\n\n if (expectedBuf.length !== actualBuf.length) {\n return false;\n }\n\n return crypto.timingSafeEqual(expectedBuf, actualBuf);\n}\n```\n \n"
requestBody:
description: Payment status notification data
required: true
content:
application/json:
schema:
type: object
properties:
eventType:
description: 'This will be set to: RefundStatus'
type: string
examples:
- RefundStatus
const: RefundStatus
transactionId:
description: A unique ID for the refund transaction.
type: string
format: uuid
examples:
- 27b2ae04-cb8e-4bcb-a976-d14a40518db1
transactionReference:
description: An 18 character refund transaction reference allocated by us, used/visible on the associated bank transaction.
type: string
examples:
- RFZ3IS1UB205Y2Y3KU
transactionStatus:
description: 'The status of the refund transaction. The refund status will be one of:
- RefundSuccess: The refund transaction was successful, money has moved from your holding account to the customer''s bank account.
- RefundFailed: The refund transaction failed.
- RefundAbandoned: The refund transaction timed out.'
type: string
examples:
- RefundSuccess
enum:
- RefundAbandoned
- RefundFailed
- RefundSuccess
externalReference:
description: Your unique reference for the refund transaction, e.g. your refundId
type: string
examples:
- refund101
required:
- eventType
- transactionId
- transactionReference
- transactionStatus
responses:
'200':
description: Return a 200 status to indicate that the data was received successfully, any other response will be deemed a failure and retried
security:
- HMAC: []
tags:
- Webhooks
payment.success:
post:
operationId: webhookPaymentSuccess
summary: Sent when a payment is successfully completed
description: "Configure this webhook in your Business Portal.\n\n\nHMAC signature verification
\n\n\nEvery webhook request includes a super-signature header so you can verify it came from Super and hasn't been tampered with.\n\n## Header format\n\nThe header contains a timestamp and a signature, separated by a comma:\n\n```super-signature: t:1669219987926,v1:vCcZMqom...base64...```\n\n- `t:` — Unix timestamp in milliseconds, generated when the request was signed\n- `v1:` — HMAC-SHA256 signature, base64-encoded\n\n## Verifying a request\n\n1. Parse the header. Split on \",\" to get the timestamp and signature parts, then split each on \":\" to extract the values.\n2. Build the signed message. Concatenate the timestamp and the raw request body, with no separator: \"message = timestamp + raw_body\"\n2. Use the raw bytes of the request body — do not re-serialize parsed JSON, as whitespace or key ordering changes will invalidate the signature.\n3. Compute the expected signature. Generate an HMAC-SHA256 of message using your webhook secret as the key, and base64-encode the result.\n4. Compare signatures. Use a constant-time comparison (e.g. crypto.timingSafeEqual in Node, hmac.compare_digest in Python) to avoid timing attacks. Reject the request if they\ndon't match.\n5. Check the timestamp. Reject requests whose timestamp is more than 5 minutes old to prevent replay attacks.\n\n## Example (Node.js)\n\n```js\nconst crypto = require('crypto');\n\nfunction verifySuperSignature(rawBody, header, secret) {\n const parts = Object.fromEntries(\n header.split(',').map((p) => { return p.split(':'); })\n );\n const timestamp = parts.t;\n const signature = parts.v1;\n\n if (Math.abs(Date.now() - Number(timestamp)) > 5 * 60 * 1000) {\n return false;\n }\n\n const expected = crypto\n .createHmac('sha256', secret)\n .update(`${timestamp}${rawBody}`)\n .digest('base64');\n\n const expectedBuf = Buffer.from(expected, 'base64');\n const actualBuf = Buffer.from(signature, 'base64');\n\n if (expectedBuf.length !== actualBuf.length) {\n return false;\n }\n\n return crypto.timingSafeEqual(expectedBuf, actualBuf);\n}\n```\n \n"
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/PaymentSuccessWebhookDto'
responses:
'200':
description: Return a 200 status to indicate that the data was received successfully
security:
- HMAC: []
tags:
- Webhooks
payment.failed:
post:
operationId: webhookPaymentFailed
summary: Sent when a payment fails
description: "Configure this webhook in your Business Portal.\n\n\nHMAC signature verification
\n\n\nEvery webhook request includes a super-signature header so you can verify it came from Super and hasn't been tampered with.\n\n## Header format\n\nThe header contains a timestamp and a signature, separated by a comma:\n\n```super-signature: t:1669219987926,v1:vCcZMqom...base64...```\n\n- `t:` — Unix timestamp in milliseconds, generated when the request was signed\n- `v1:` — HMAC-SHA256 signature, base64-encoded\n\n## Verifying a request\n\n1. Parse the header. Split on \",\" to get the timestamp and signature parts, then split each on \":\" to extract the values.\n2. Build the signed message. Concatenate the timestamp and the raw request body, with no separator: \"message = timestamp + raw_body\"\n2. Use the raw bytes of the request body — do not re-serialize parsed JSON, as whitespace or key ordering changes will invalidate the signature.\n3. Compute the expected signature. Generate an HMAC-SHA256 of message using your webhook secret as the key, and base64-encode the result.\n4. Compare signatures. Use a constant-time comparison (e.g. crypto.timingSafeEqual in Node, hmac.compare_digest in Python) to avoid timing attacks. Reject the request if they\ndon't match.\n5. Check the timestamp. Reject requests whose timestamp is more than 5 minutes old to prevent replay attacks.\n\n## Example (Node.js)\n\n```js\nconst crypto = require('crypto');\n\nfunction verifySuperSignature(rawBody, header, secret) {\n const parts = Object.fromEntries(\n header.split(',').map((p) => { return p.split(':'); })\n );\n const timestamp = parts.t;\n const signature = parts.v1;\n\n if (Math.abs(Date.now() - Number(timestamp)) > 5 * 60 * 1000) {\n return false;\n }\n\n const expected = crypto\n .createHmac('sha256', secret)\n .update(`${timestamp}${rawBody}`)\n .digest('base64');\n\n const expectedBuf = Buffer.from(expected, 'base64');\n const actualBuf = Buffer.from(signature, 'base64');\n\n if (expectedBuf.length !== actualBuf.length) {\n return false;\n }\n\n return crypto.timingSafeEqual(expectedBuf, actualBuf);\n}\n```\n \n"
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/PaymentFailedWebhookDto'
responses:
'200':
description: Return a 200 status to indicate that the data was received successfully
security:
- HMAC: []
tags:
- Webhooks
refund.success:
post:
operationId: webhookRefundSuccess
summary: Sent when a refund is successfully completed
description: "Configure this webhook in your Business Portal.\n\n\nHMAC signature verification
\n\n\nEvery webhook request includes a super-signature header so you can verify it came from Super and hasn't been tampered with.\n\n## Header format\n\nThe header contains a timestamp and a signature, separated by a comma:\n\n```super-signature: t:1669219987926,v1:vCcZMqom...base64...```\n\n- `t:` — Unix timestamp in milliseconds, generated when the request was signed\n- `v1:` — HMAC-SHA256 signature, base64-encoded\n\n## Verifying a request\n\n1. Parse the header. Split on \",\" to get the timestamp and signature parts, then split each on \":\" to extract the values.\n2. Build the signed message. Concatenate the timestamp and the raw request body, with no separator: \"message = timestamp + raw_body\"\n2. Use the raw bytes of the request body — do not re-serialize parsed JSON, as whitespace or key ordering changes will invalidate the signature.\n3. Compute the expected signature. Generate an HMAC-SHA256 of message using your webhook secret as the key, and base64-encode the result.\n4. Compare signatures. Use a constant-time comparison (e.g. crypto.timingSafeEqual in Node, hmac.compare_digest in Python) to avoid timing attacks. Reject the request if they\ndon't match.\n5. Check the timestamp. Reject requests whose timestamp is more than 5 minutes old to prevent replay attacks.\n\n## Example (Node.js)\n\n```js\nconst crypto = require('crypto');\n\nfunction verifySuperSignature(rawBody, header, secret) {\n const parts = Object.fromEntries(\n header.split(',').map((p) => { return p.split(':'); })\n );\n const timestamp = parts.t;\n const signature = parts.v1;\n\n if (Math.abs(Date.now() - Number(timestamp)) > 5 * 60 * 1000) {\n return false;\n }\n\n const expected = crypto\n .createHmac('sha256', secret)\n .update(`${timestamp}${rawBody}`)\n .digest('base64');\n\n const expectedBuf = Buffer.from(expected, 'base64');\n const actualBuf = Buffer.from(signature, 'base64');\n\n if (expectedBuf.length !== actualBuf.length) {\n return false;\n }\n\n return crypto.timingSafeEqual(expectedBuf, actualBuf);\n}\n```\n \n"
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/RefundSuccessWebhookDto'
responses:
'200':
description: Return a 200 status to indicate that the data was received successfully
security:
- HMAC: []
tags:
- Webhooks
refund.failed:
post:
operationId: webhookRefundFailed
summary: Sent when a refund fails
description: "Configure this webhook in your Business Portal.\n\n\nHMAC signature verification
\n\n\nEvery webhook request includes a super-signature header so you can verify it came from Super and hasn't been tampered with.\n\n## Header format\n\nThe header contains a timestamp and a signature, separated by a comma:\n\n```super-signature: t:1669219987926,v1:vCcZMqom...base64...```\n\n- `t:` — Unix timestamp in milliseconds, generated when the request was signed\n- `v1:` — HMAC-SHA256 signature, base64-encoded\n\n## Verifying a request\n\n1. Parse the header. Split on \",\" to get the timestamp and signature parts, then split each on \":\" to extract the values.\n2. Build the signed message. Concatenate the timestamp and the raw request body, with no separator: \"message = timestamp + raw_body\"\n2. Use the raw bytes of the request body — do not re-serialize parsed JSON, as whitespace or key ordering changes will invalidate the signature.\n3. Compute the expected signature. Generate an HMAC-SHA256 of message using your webhook secret as the key, and base64-encode the result.\n4. Compare signatures. Use a constant-time comparison (e.g. crypto.timingSafeEqual in Node, hmac.compare_digest in Python) to avoid timing attacks. Reject the request if they\ndon't match.\n5. Check the timestamp. Reject requests whose timestamp is more than 5 minutes old to prevent replay attacks.\n\n## Example (Node.js)\n\n```js\nconst crypto = require('crypto');\n\nfunction verifySuperSignature(rawBody, header, secret) {\n const parts = Object.fromEntries(\n header.split(',').map((p) => { return p.split(':'); })\n );\n const timestamp = parts.t;\n const signature = parts.v1;\n\n if (Math.abs(Date.now() - Number(timestamp)) > 5 * 60 * 1000) {\n return false;\n }\n\n const expected = crypto\n .createHmac('sha256', secret)\n .update(`${timestamp}${rawBody}`)\n .digest('base64');\n\n const expectedBuf = Buffer.from(expected, 'base64');\n const actualBuf = Buffer.from(signature, 'base64');\n\n if (expectedBuf.length !== actualBuf.length) {\n return false;\n }\n\n return crypto.timingSafeEqual(expectedBuf, actualBuf);\n}\n```\n \n"
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/RefundFailedWebhookDto'
responses:
'200':
description: Return a 200 status to indicate that the data was received successfully
security:
- HMAC: []
tags:
- Webhooks
customer.payment_method.requires_action:
post:
operationId: webhookCustomerPaymentMethodRequiresAction
summary: Sent when a customer payment method is created and requires action to complete setup
description: "Configure this webhook in your Business Portal.\n\n\nHMAC signature verification
\n\n\nEvery webhook request includes a super-signature header so you can verify it came from Super and hasn't been tampered with.\n\n## Header format\n\nThe header contains a timestamp and a signature, separated by a comma:\n\n```super-signature: t:1669219987926,v1:vCcZMqom...base64...```\n\n- `t:` — Unix timestamp in milliseconds, generated when the request was signed\n- `v1:` — HMAC-SHA256 signature, base64-encoded\n\n## Verifying a request\n\n1. Parse the header. Split on \",\" to get the timestamp and signature parts, then split each on \":\" to extract the values.\n2. Build the signed message. Concatenate the timestamp and the raw request body, with no separator: \"message = timestamp + raw_body\"\n2. Use the raw bytes of the request body — do not re-serialize parsed JSON, as whitespace or key ordering changes will invalidate the signature.\n3. Compute the expected signature. Generate an HMAC-SHA256 of message using your webhook secret as the key, and base64-encode the result.\n4. Compare signatures. Use a constant-time comparison (e.g. crypto.timingSafeEqual in Node, hmac.compare_digest in Python) to avoid timing attacks. Reject the request if they\ndon't match.\n5. Check the timestamp. Reject requests whose timestamp is more than 5 minutes old to prevent replay attacks.\n\n## Example (Node.js)\n\n```js\nconst crypto = require('crypto');\n\nfunction verifySuperSignature(rawBody, header, secret) {\n const parts = Object.fromEntries(\n header.split(',').map((p) => { return p.split(':'); })\n );\n const timestamp = parts.t;\n const signature = parts.v1;\n\n if (Math.abs(Date.now() - Number(timestamp)) > 5 * 60 * 1000) {\n return false;\n }\n\n const expected = crypto\n .createHmac('sha256', secret)\n .update(`${timestamp}${rawBody}`)\n .digest('base64');\n\n const expectedBuf = Buffer.from(expected, 'base64');\n const actualBuf = Buffer.from(signature, 'base64');\n\n if (expectedBuf.length !== actualBuf.length) {\n return false;\n }\n\n return crypto.timingSafeEqual(expectedBuf, actualBuf);\n}\n```\n \n"
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CustomerPaymentMethodRequiresActionWebhookDto'
responses:
'200':
description: Return a 200 status to indicate that the data was received successfully
security:
- HMAC: []
tags:
- Webhooks
customer.payment_method.enabled:
post:
operationId: webhookCustomerPaymentMethodEnabled
summary: Sent when a customer payment method becomes enabled and ready for use
description: "Configure this webhook in your Business Portal.\n\n\nHMAC signature verification
\n\n\nEvery webhook request includes a super-signature header so you can verify it came from Super and hasn't been tampered with.\n\n## Header format\n\nThe header contains a timestamp and a signature, separated by a comma:\n\n```super-signature: t:1669219987926,v1:vCcZMqom...base64...```\n\n- `t:` — Unix timestamp in milliseconds, generated when the request was signed\n- `v1:` — HMAC-SHA256 signature, base64-encoded\n\n## Verifying a request\n\n1. Parse the header. Split on \",\" to get the timestamp and signature parts, then split each on \":\" to extract the values.\n2. Build the signed message. Concatenate the timestamp and the raw request body, with no separator: \"message = timestamp + raw_body\"\n2. Use the raw bytes of the request body — do not re-serialize parsed JSON, as whitespace or key ordering changes will invalidate the signature.\n3. Compute the expected signature. Generate an HMAC-SHA256 of message using your webhook secret as the key, and base64-encode the result.\n4. Compare signatures. Use a constant-time comparison (e.g. crypto.timingSafeEqual in Node, hmac.compare_digest in Python) to avoid timing attacks. Reject the request if they\ndon't match.\n5. Check the timestamp. Reject requests whose timestamp is more than 5 minutes old to prevent replay attacks.\n\n## Example (Node.js)\n\n```js\nconst crypto = require('crypto');\n\nfunction verifySuperSignature(rawBody, header, secret) {\n const parts = Object.fromEntries(\n header.split(',').map((p) => { return p.split(':'); })\n );\n const timestamp = parts.t;\n const signature = parts.v1;\n\n if (Math.abs(Date.now() - Number(timestamp)) > 5 * 60 * 1000) {\n return false;\n }\n\n const expected = crypto\n .createHmac('sha256', secret)\n .update(`${timestamp}${rawBody}`)\n .digest('base64');\n\n const expectedBuf = Buffer.from(expected, 'base64');\n const actualBuf = Buffer.from(signature, 'base64');\n\n if (expectedBuf.length !== actualBuf.length) {\n return false;\n }\n\n return crypto.timingSafeEqual(expectedBuf, actualBuf);\n}\n```\n \n"
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CustomerPaymentMethodEnabledWebhookDto'
responses:
'200':
description: Return a 200 status to indicate that the data was received successfully
security:
- HMAC: []
tags:
- Webhooks
customer.payment_method.disabled:
post:
operationId: webhookCustomerPaymentMethodDisabled
summary: Sent when a customer payment method is disabled
description: "Configure this webhook in your Business Portal.\n\n\nHMAC signature verification
\n\n\nEvery webhook request includes a super-signature header so you can verify it came from Super and hasn't been tampered with.\n\n## Header format\n\nThe header contains a timestamp and a signature, separated by a comma:\n\n```super-signature: t:1669219987926,v1:vCcZMqom...base64...```\n\n- `t:` — Unix timestamp in milliseconds, generated when the request was signed\n- `v1:` — HMAC-SHA256 signature, base64-encoded\n\n## Verifying a request\n\n1. Parse the header. Split on \",\" to get the timestamp and signature parts, then split each on \":\" to extract the values.\n2. Build the signed message. Concatenate the timestamp and the raw request body, with no separator: \"message = timestamp + raw_body\"\n2. Use the raw bytes of the request body — do not re-serialize parsed JSON, as whitespace or key ordering changes will invalidate the signature.\n3. Compute the expected signature. Generate an HMAC-SHA256 of message using your webhook secret as the key, and base64-encode the result.\n4. Compare signatures. Use a constant-time comparison (e.g. crypto.timingSafeEqual in Node, hmac.compare_digest in Python) to avoid timing attacks. Reject the request if they\ndon't match.\n5. Check the timestamp. Reject requests whose timestamp is more than 5 minutes old to prevent replay attacks.\n\n## Example (Node.js)\n\n```js\nconst crypto = require('crypto');\n\nfunction verifySuperSignature(rawBody, header, secret) {\n const parts = Object.fromEntries(\n header.split(',').map((p) => { return p.split(':'); })\n );\n const timestamp = parts.t;\n const signature = parts.v1;\n\n if (Math.abs(Date.now() - Number(timestamp)) > 5 * 60 * 1000) {\n return false;\n }\n\n const expected = crypto\n .createHmac('sha256', secret)\n .update(`${timestamp}${rawBody}`)\n .digest('base64');\n\n const expectedBuf = Buffer.from(expected, 'base64');\n const actualBuf = Buffer.from(signature, 'base64');\n\n if (expectedBuf.length !== actualBuf.length) {\n return false;\n }\n\n return crypto.timingSafeEqual(expectedBuf, actualBuf);\n}\n```\n \n"
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CustomerPaymentMethodDisabledWebhookDto'
responses:
'200':
description: Return a 200 status to indicate that the data was received successfully
security:
- HMAC: []
tags:
- Webhooks
components:
schemas:
CustomerPaymentMethodDisabledWebhookDto:
type: object
properties:
eventType:
type: string
const: customer.payment_method.disabled
eventId:
type: string
eventDatetime:
type: string
format: date-time
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$
data:
type: object
properties:
paymentMethodId:
type: string
customerId:
type: string
merchantId:
type: string
type:
type: string
usage:
type: string
status:
type: string
required:
- paymentMethodId
- customerId
- merchantId
- type
- usage
- status
required:
- eventType
- eventId
- eventDatetime
- data
RefundFailedWebhookDto:
type: object
properties:
eventType:
type: string
const: refund.failed
eventId:
type: string
eventDatetime:
type: string
format: date-time
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$
data:
type: object
properties:
brandId:
type: string
refundId:
type: string
status:
type: string
refundReference:
type: string
externalReference:
type: string
originatingPaymentId:
type: string
originatingPaymentExternalReference:
type: string
originatingPaymentSource:
type: string
amount:
type: object
properties:
amount:
type: number
amountMultiplier:
type: number
currency:
type: string
required:
- amount
- amountMultiplier
- currency
refundComponents:
type: object
properties:
merchantAmount:
type: object
properties:
amount:
type: number
amountMultiplier:
type: number
currency:
type: string
required:
- amount
- amountMultiplier
- currency
customerAmount:
type: object
properties:
amount:
type: number
amountMultiplier:
type: number
currency:
type: string
required:
- amount
- amountMultiplier
- currency
superAmount:
type: object
properties:
amount:
type: number
amountMultiplier:
type: number
currency:
type: string
required:
- amount
- amountMultiplier
- currency
required:
- merchantAmount
- customerAmount
- superAmount
required:
- brandId
- refundId
- status
- refundReference
- externalReference
- originatingPaymentId
- originatingPaymentExternalReference
- originatingPaymentSource
- amount
- refundComponents
required:
- eventType
- eventId
- eventDatetime
- data
PaymentFailedWebhookDto:
type: object
properties:
eventType:
type: string
const: payment.failed
eventId:
type: string
eventDatetime:
type: string
format: date-time
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$
data:
type: object
properties:
brandId:
type: string
paymentInitiatorId:
type: string
paymentId:
type: string
paymentReference:
type: string
source:
type: string
amount:
type: object
properties:
amount:
type: number
amountMultiplier:
type: number
currency:
type: string
required:
- amount
- amountMultiplier
- currency
status:
type: string
fundingSummary:
anyOf:
- type: object
properties:
cashPayableToMerchant:
type: object
properties:
amount:
type: number
amountMultiplier:
type: number
currency:
type: string
required:
- amount
- amountMultiplier
- currency
customerFundedAmount:
type: object
properties:
amount:
type: number
amountMultiplier:
type: number
currency:
type: string
required:
- amount
- amountMultiplier
- currency
superFundedAmount:
type: object
properties:
amount:
type: number
amountMultiplier:
type: number
currency:
type: string
required:
- amount
- amountMultiplier
- currency
merchantFundedAmount:
type: object
properties:
amount:
type: number
amountMultiplier:
type: number
currency:
type: string
required:
- amount
- amountMultiplier
- currency
required:
- cashPayableToMerchant
- customerFundedAmount
- superFundedAmount
- merchantFundedAmount
- type: 'null'
surchargeAmount:
anyOf:
- type: object
properties:
amount:
type: number
amountMultiplier:
type: number
currency:
type: string
required:
- amount
- amountMultiplier
- currency
- type: 'null'
externalReference:
type: string
required:
- brandId
- paymentInitiatorId
- paymentId
- paymentReference
- source
- amount
- status
- fundingSummary
- surchargeAmount
- externalReference
required:
- eventType
- eventId
- eventDatetime
- data
CustomerPaymentMethodRequiresActionWebhookDto:
type: object
properties:
eventType:
type: string
const: customer.payment_method.requires_action
eventId:
type: string
eventDatetime:
type: string
format: date-time
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$
data:
type: object
properties:
paymentMethodId:
type: string
customerId:
type: string
merchantId:
type: string
type:
type: string
usage:
type: string
status:
type: string
required:
- paymentMethodId
- customerId
- merchantId
- type
- usage
- status
required:
- eventType
- eventId
- eventDatetime
- data
RefundSuccessWebhookDto:
type: object
properties:
eventType:
type: string
const: refund.success
eventId:
type: string
eventDatetime:
type: string
format: date-time
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$
data:
type: object
properties:
brandId:
type: string
refundId:
type: string
status:
type: string
refundReference:
type: string
externalReference:
type: string
originatingPaymentId:
type: string
originatingPaymentExternalReference:
type: string
originatingPaymentSource:
type: string
amount:
type: object
properties:
amount:
type: number
amountMultiplier:
type: number
currency:
type: string
required:
- amount
- amountMultiplier
- currency
refundComponents:
type: object
properties:
merchantAmount:
type: object
properties:
amount:
type: number
amountMultiplier:
type: number
currency:
type: string
required:
- amount
- amountMultiplier
- currency
customerAmount:
type: object
properties:
amount:
type: number
amountMultiplier:
type: number
currency:
type: string
required:
- amount
- amountMultiplier
- currency
superAmount:
type: object
properties:
amount:
type: number
amountMultiplier:
type: number
currency:
type: string
required:
- amount
- amountMultiplier
- currency
required:
- merchantAmount
- customerAmount
- superAmount
required:
- brandId
- refundId
- status
- refundReference
- externalReference
- originatingPaymentId
- originatingPaymentExternalReference
- originatingPaymentSource
- amount
- refundComponents
required:
- eventType
- eventId
- eventDatetime
- data
CustomerPaymentMethodEnabledWebhookDto:
type: object
properties:
eventType:
type: string
const: customer.payment_method.enabled
eventId:
type: string
eventDatetime:
type: string
format: date-time
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$
data:
type: object
properties:
paymentMethodId:
type: string
customerId:
type: string
merchantId:
type: string
type:
type: string
usage:
type: string
status:
type: string
required:
- paymentMethodId
- customerId
- merchantId
- type
- usage
- status
required:
- eventType
- eventId
- eventDatetime
- data
PaymentSuccessWebhookDto:
type: object
properties:
eventType:
type: string
const: payment.success
eventId:
type: string
eventDatetime:
type: string
format: date-time
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$
data:
type: object
properties:
brandId:
type: string
paymentInitiatorId:
type: string
paymentId:
type: string
paymentReference:
type: string
source:
type: string
amount:
type: object
properties:
amount:
type: number
amountMultiplier:
type: number
currency:
type: string
required:
- amount
- amountMultiplier
- currency
status:
type: string
fundingSummary:
anyOf:
- type: object
properties:
cashPayableToMerchant:
type: object
properties:
amount:
type: number
amountMultiplier:
type: number
currency:
type: string
required:
- amount
- amountMultiplier
- currency
customerFundedAmount:
type: object
properties:
amount:
type: number
amountMultiplier:
type: number
currency:
type: string
required:
- amount
- amountMultiplier
- currency
superFundedAmount:
type: object
properties:
amount:
type: number
amountMultiplier:
type: number
currency:
type: string
required:
- amount
- amountMultiplier
- currency
merchantFundedAmount:
type: object
properties:
amount:
type: number
amountMultiplier:
type: number
currency:
type: string
required:
- amount
- amountMultiplier
- currency
required:
- cashPayableToMerchant
- customerFundedAmount
- superFundedAmount
- merchantFundedAmount
- type: 'null'
surchargeAmount:
anyOf:
- type: object
properties:
amount:
type: number
amountMultiplier:
type: number
currency:
type: string
required:
- amount
- amountMultiplier
- currency
- type: 'null'
externalReference:
type: string
required:
- brandId
- paymentInitiatorId
- paymentId
- paymentReference
- source
- amount
- status
- fundingSummary
- surchargeAmount
- externalReference
required:
- eventType
- eventId
- eventDatetime
- data
securitySchemes:
api_key:
type: apiKey
in: header
name: Authorization
description: Merchant API key
HMAC:
type: apiKey
in: header
name: super-signature
description: Header used to validate the webhook request