Signature Verification

Every webhook request from Terminal3 includes a signature so you can verify it is authentic and has not been tampered with. It is your responsibility to verify this signature before processing the request.

Each webhook request contains the following header:

X-Webhook-Signature: t=1688000000,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd

The header has two components, separated by a comma:

Component Description
t Unix timestamp (in seconds) when the request was signed.
v1 HMAC-SHA256 hex digest of the signing string.

How to Verify

Step 1: Parse the header

Split the X-Webhook-Signature value by , and extract the t and v1 values.

Step 2: Build the signing string

Concatenate the timestamp t, a literal period ., and the raw request body (the JSON payload exactly as received, with no parsing or reformatting):

1688000000.{"order_id":123,"uid":"user123","status":"completed"}

Step 3: Compute the expected signature

Calculate the HMAC-SHA256 hex digest of the signing string using your Signing Secret (the whsec_ prefixed key found in your Webhook Endpoint Configuration):

expected = hex(hmac_sha256(signing_string, endpoint_secret))

Step 4: Compare signatures

Compare your computed signature with the v1 value from the header. Use a constant-time comparison function (e.g. hash_equals in PHP, hmac.compare_digest in Python) to prevent timing attacks.

Compare t against the current time. Reject the request if the timestamp is too far in the past (e.g. more than 5 minutes) to protect against replay attacks.

Example

Given:

  • Signing Secret: whsec_test123
  • Raw body: {"order_id":123,"uid":"user123"}
  • Header: X-Webhook-Signature: t=1688000000,v1=abc123...

Verification:

signing_string = "1688000000.{\"order_id\":123,\"uid\":\"user123\"}"
expected       = hex(hmac_sha256(signing_string, "whsec_test123"))
verified       = constant_time_equal(expected, "abc123...")