Skip to main content
When Suki calls your webhook, it proves the call is genuine by sending a signature you can recompute on your side. If the signature matches, the request really used your secret key and the body was not altered on the way to you.
Verify every webhook before you parse the JSON body. Unverified POSTs can expose your integration to spoofed events and tampered payloads.

What you need

Think of it in three plain pieces:
PieceWhat it is
Secret keyA password-like string Suki puts on your partner record and shares with you during partner onboarding. Only your server should know it.
generated-at headerA number: Unix time in milliseconds when Suki built this request. You need it as part of the signed text.
X-API-Key headerThe expected signature for this request, already turned into hex text. Your job is to compute the same hex and see if they are equal.
Suki sends each notification as a POST with Content-Type: application/json. Your handler reads the raw body and the two headers above before you trust the payload.

How to verify on your server

Perform the following steps on your server:

Read the raw body first

Read the request body before you parse JSON. Keep the exact bytes or string Suki sent (no pretty-printing, no trimming, no changing spaces).

Read the generated-at header

Read the generated-at header exactly as the string Suki sent (the millisecond timestamp).

Build the string to sign

Join generated-at + : + raw body into one long string. Example shape: 1765977748432:{"status":"success",...} (your timestamp and JSON will differ).

Run HMAC-SHA-256

Run HMAC-SHA-256 using your secret key as the key and that long string as the data. HMAC-SHA-256 is a standard “sign this text with this secret” operation; every language has a library for it.

Encode the digest as hex

Turn the HMAC output into hex the same way Suki does (usually lowercase hex; if your comparison fails, ask your Suki contact whether casing matters).

Compare to X-API-Key

Compare your hex to the X-API-Key header. Use a constant-time compare if your framework offers one, so attackers cannot guess the signature byte by byte.
If the values do not match, stop and return 4xx. If they match, you can safely parse the JSON and run your business logic. Pseudocode
raw_body = read request body as received (string or bytes)
timestamp = request header "generated-at" exactly as sent
message = timestamp + ":" + raw_body

expected_signature_hex = request header "X-API-Key"
computed_signature_hex = hex( HMAC_SHA256(key = secret_key, data = message) )

if not constant_time_equal(computed_signature_hex, expected_signature_hex):
    return 401 or 400
# else: parse JSON and continue
Read the raw request body before JSON parsing. Framework middleware that auto-parses JSON first will break verification because the signature covers the exact bytes Suki sent.

Code examples

The examples below follow the pseudocode above line for line.
import hashlib
import hmac
import json

from flask import Flask, jsonify, request

app = Flask(__name__)

# Partner secret key from onboarding
secret_key = "<partner_secret_key>"


@app.route("/webhooks/notification", methods=["POST"])
def handle_webhook():
    raw_body = request.get_data(as_text=True)
    timestamp = request.headers.get("generated-at", "")
    message = timestamp + ":" + raw_body

    expected_signature_hex = request.headers.get("X-API-Key", "")
    computed_signature_hex = hmac.new(
        secret_key.encode("utf-8"),
        message.encode("utf-8"),
        hashlib.sha256,
    ).hexdigest()

    if not hmac.compare_digest(computed_signature_hex, expected_signature_hex):
        return jsonify({"error": "Invalid signature"}), 401  # or 400

    # else: parse JSON and continue
    data = json.loads(raw_body)
    return jsonify({"message": "Notification received"}), 200
For a full handler that branches on status and processes _links, refer to the Asynchronous notifications (webhook) API reference.

After verification

Once the signature matches:
  1. Parse the JSON body.
  2. Read the top-level status field and branch on "success" or "failure".
  3. Use session_id, encounter_id, and _links as described in Payload & Response.
Return 2xx (for example, 200) after you accept the notification so Suki can consider it delivered. You can run follow-up API calls or database work after that response if needed.

Security best practices

Implement HMAC-SHA-256 verification using your secret key, the generated-at header, the raw body, and the X-API-Key header as described above.
After the signature matches, check the JSON shape and status. Reject malformed or unverified requests with an appropriate 4xx response.
Keep the partner secret key in a secrets manager (for example AWS Secrets Manager or Azure Key Vault), not in source control.
Your callback URL must use HTTPS with TLS 1.2 or higher. Suki will not send webhooks to HTTP URLs. See Configuration.
For platform-wide guidance, refer to Security & best practices and Authentication FAQs.

Next steps

Refer to Payload & Response for payload structure, example JSON bodies, implementation tips, and follow-up API response codes. Refer to Event types for session completion, failure, timeout, and cancellation events.
Last modified on June 12, 2026