KYT decision webhook

Verifying requests

How to authenticate an incoming KYT request.

Every inbound KYT request carries two authentication headers. The merchant's endpoint MUST verify these headers before processing the payload. Tekmerion MUST NOT dispatch a KYT request without both headers.

Authentication headers

HeaderFormatDescription
X-Tekmerion-KYT-Signaturev1=<hex_digest>HMAC-SHA256 digest with version prefix. v1 is the current version token. <hex_digest> is 64 lowercase hex characters.
X-Tekmerion-KYT-Timestamp<unix_epoch_seconds>Unix seconds at which Tekmerion dispatched the request. Decimal integer, no fractional seconds.

Verification procedure

Verify in this order:

Step 1 — Extract headers. Read X-Tekmerion-KYT-Signature and X-Tekmerion-KYT-Timestamp from the incoming request. If either header is absent, reject the request (HTTP 400). Do not attempt partial verification.

Step 2 — Parse version token. Split X-Tekmerion-KYT-Signature on the first = character. The left part is the version token; the right part is the digest. If the token is not v1, reject the request as an unsupported version.

Step 3 — Replay-window check. Compare X-Tekmerion-KYT-Timestamp against current time. Reject if:

abs(now_unix_seconds − X-Tekmerion-KYT-Timestamp) > 300

Perform this check before HMAC computation to avoid wasting compute on stale or replayed requests.

Step 4 — Reconstruct the base string. Concatenate as a single string with no added whitespace:

v1:{timestamp}:{raw_body}

Where {timestamp} is the exact decimal integer from X-Tekmerion-KYT-Timestamp and {raw_body} is the exact raw UTF-8 request body bytes as received — not re-serialized or whitespace-normalized.

Step 5 — Compute the expected digest. Compute HMAC-SHA256 over the UTF-8 bytes of the base string using the signing secret configured for your KYT endpoint. Encode the output as lowercase hexadecimal (64 characters).

Step 6 — Constant-time compare. Compare the expected digest against the digest extracted in Step 2 using a constant-time comparison function. If they differ, reject the request.

Step 7 — Process. Only after passing all preceding steps, parse and process the JSON payload.

Constant-time comparison

Standard string equality (==, equals(), strcmp()) MUST NOT be used. These functions are vulnerable to timing side-channel attacks that allow recovering the valid signature byte by byte.

If the received digest length is not 64 characters, reject immediately without comparison.

LanguageConstant-time function
Pythonhmac.compare_digest(a, b)
Node.jscrypto.timingSafeEqual(a, b)
Gosubtle.ConstantTimeCompare(a, b)
RubyActiveSupport::SecurityUtils.secure_compare(a, b)
PHPhash_equals($a, $b)
JavaMessageDigest.isEqual(a, b)

Worked example

Given:

  • X-Tekmerion-KYT-Timestamp: 1714000000
  • Raw body: {"kyt_invocation_id":"kyt_01","payment_attempt_id":"att_01","payment_intent_id":"pi_01","merchant_id":"m_01","invocation_no":1,...}

Base string (single line, exactly as constructed):

v1:1714000000:{"kyt_invocation_id":"kyt_01","payment_attempt_id":"att_01","payment_intent_id":"pi_01","merchant_id":"m_01","invocation_no":1,...}

HMAC-SHA256 is computed over the UTF-8 encoding of that string using the endpoint signing secret as key.

Header prefix distinction

The X-Tekmerion-KYT- prefix identifies KYT invocation headers and distinguishes them from notification delivery webhook headers (X-Tekmerion-). The verification algorithm is identical, but:

  • KYT requests use the signing secret registered with the KYT endpoint configuration.
  • Notification webhooks use the signing secret registered with the notification endpoint.

Secrets are not shared between surfaces. A KYT signing secret MUST NOT be used to verify notification webhooks, and vice versa.

On this page