Skip to main content

Verifying webhook signatures

You should use the following headers to verify authenticity of each webhook request.
KeyTypeDescription
X-Subtotal-SignaturestringHMAC of payload using shared secret
X-Subtotal-TimestampstringThe timestamp of the webhook request

Step 1: Prepare the signed_payload string

Concatenate the following to create the signed_payload string:
  • The timestamp (as a string)
  • A . character
  • The request body (as a string)

Step 2: Compute the expected_signature

Compute the expected_signature using the HMAC-SHA256 hash function. Use the webhook destination’s signing_secret as the key, and use the signed_payload string as the message.
The signing_secret is obtained when a new webhook destination is created in the Subtotal Dashboard.

Step 3: Compare the signatures

Compare the expected_signature to the X-Subtotal-Signature. To prevent replay attacks, compare the received timestamp to the current time and reject requests outside your tolerance window. To protect against timing attacks, use a constant-time string comparison.

Properties included with every webhook request

These properties are included with each webhook request:
KeyTypeDescription
typestringThe webhook event type
idstringIdentifier for the webhook
payloadstringThe payload of the webhook

Event types

Below are all supported event types and their cooresponding payload structures.

connection.activated

Connections are activated when a customer successfully links their account.
KeyTypeDescription
payload.connection.connection_idstringIdentifier for the connection
payload.connection.customer_idstringIdentifier for the associated customer
payload.connection.emailstringEmail address of the associated customer
payload.connection.mobilestringMobile phone number of the associated customer
payload.connection.statusstringCurrent status of the connection
payload.connection.retailer_idstringIdentifier for the retailer in the connection

connection.unauthenticated

Connections move from activated to unauthenticated when a customer needs to re-authenticate their account. For example, when a customer changes their password with the retailer.
KeyTypeDescription
payload.connection.connection_idstringIdentifier for the connection
payload.connection.customer_idstringIdentifier for the associated customer
payload.connection.emailstringEmail address of the associated customer
payload.connection.mobilestringMobile phone number of the associated customer
payload.connection.statusstringCurrent status of the connection
payload.connection.retailer_idstringIdentifier for the retailer in the connection

purchase.created

The purchase.created event is sent when a new purchase is received from a customer’s connected retailer account. We recommend using this event when you need to process customer purchases for your use case.
KeyTypeDescription
payload.connection.connection_idstringIdentifier for the connection
payload.connection.customer_idstringIdentifier for the associated customer
payload.connection.emailstringEmail address of the associated customer
payload.connection.mobilestringMobile phone number of the associated customer
payload.connection.statusstringCurrent status of the connection
payload.connection.retailer_idstringIdentifier for retailer in the connection
payload.purchase.purchase_idstringIdentifier for the purchase
payload.purchase.datestringDate of the purchase (ISO 8601)
payload.purchase.totalnumberTotal amount of the purchase
payload.purchase.subtotalnumberSubtotal before tax
payload.purchase.taxnumberTax amount
payload.purchase.items[].namestringName of the item
payload.purchase.items[].descriptionstringDescription of the item
payload.purchase.items[].quantitynumberQuantity
payload.purchase.items[].pricenumberPrice of the item
payload.purchase.items[].product_idstringIdentifier for the product
payload.purchase.items[].upcstringUniversal identifier for the product purchased (UPC)
{
  "id": "01J51S0JYV6N7K1030CV1ZBDOW",
  "type": "purchase.created",
  "payload": {
    "connection": {
      "connection_id": "01J51S0JYV6N7K1030CV1ZKSCA",
      "customer_id": "01K8HDK6Y9FXM7ES4NJSHZDEKF",
      "retailer_id": "walmart",
      "email": "[email protected]",
      "mobile": "+123456789",
      "status": "active"
    },
    "purchase": {
      "purchase_id": "01J51S0JYV6N7K1030CV1ZKSJH",
      "date": "2025-10-08T14:23:00Z",
      "total": 47.85,
      "subtotal": 43.50,
      "tax": 4.35,
      "items": [
        {
          "item_id": "01JV7ZDWC9GN1VPR41K3BY08X9",
          "price": 5.99,
          "quantity": 2,
          "product": {
            "product_id": "01J51S0JYV6N7K1030CV1ZKSCA", 
            "name": "Sparkling Water 12pk",
            "description": "Sparkling Water 12-Pack - 12 fl oz bottles",
            "upc": "001234567890",
            "brand": "la-croix"
          }
        },
        {
          "item_id": "01K3KS9Q4522E3HG444E4XH1R0",
          "price": 12.99,
          "quantity": 1,
          "product": {
            "product_id": "01J51S0JYV6N7K1030CV1ZKSCA",
            "name": "Trail Mix",
            "description": "Trail Mix Family Size", 
            "upc": "009876543210",
            "brand": "harvest-one"
          }
        },
        {
          "item_id": "01JV7ZDWC9GN1VPR41K3BY08X9"
          "price": 18.53,
          "quantity": 1,
          "product": {
            "product_id": "01J51S0JYV6N7K1030CV1ZKSCA",
            "name": "Face Cream",
            "description": "Moisturizing Face Cream",
            "upc": "011122233344",
            "brand": "glowww"
          }
        }
      ]
    }
  }
}