Super Payments Webhooks API

The Webhooks API from Super Payments — 0 operation(s) for webhooks.

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/super-payments-webhooks-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no email required.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

super-payments-webhooks-api-openapi.yml Raw ↑
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<details>\n<summary>HMAC signature verification</summary>\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</details>\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<details>\n<summary>HMAC signature verification</summary>\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</details>\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<details>\n<summary>HMAC signature verification</summary>\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</details>\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<details>\n<summary>HMAC signature verification</summary>\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</details>\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<details>\n<summary>HMAC signature verification</summary>\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</details>\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<details>\n<summary>HMAC signature verification</summary>\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</details>\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<details>\n<summary>HMAC signature verification</summary>\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</details>\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<details>\n<summary>HMAC signature verification</summary>\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</details>\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<details>\n<summary>HMAC signature verification</summary>\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</details>\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: s

# --- truncated at 32 KB (48 KB total) ---
# Full source: https://raw.githubusercontent.com/api-evangelist/super-payments/refs/heads/main/openapi/super-payments-webhooks-api-openapi.yml