API v1

Verify callback signatures

Use the signing secret from registration to check that every event comes from us.

View Markdown
On this page

Every event carries a Chenvoya-Signature header. Verify the signature before you read the body; when verification fails, answer with a non-2xx status and drop the request.

Signature format

Chenvoya-Signature: t=1793167400,v1=<64 lowercase hexadecimal digits>

t is the signing time in Unix seconds and v1 is a lowercase hexadecimal HMAC-SHA256.

Steps

  1. Base64url-decode the signing_secret from registration to get the 32-byte verification key.
  2. Split t and v1 out of Chenvoya-Signature.
  3. Reject t when it differs from your clock by more than 5 minutes.
  4. Compute HMAC-SHA256 with the verification key over chenvoya.v1., t, a dot . and the original body bytes.
  5. Write the result as lowercase hexadecimal and compare it with v1 in constant time.
  6. Then deduplicate on event_id in the body, and reject events whose created_at is more than 7 days and 5 minutes old or more than 5 minutes in the future.

Sample code

import base64
import hashlib
import hmac
import time

def verify(signing_secret, header, body):
    fields = {}
    for item in header.split(","):
        name, _, value = item.strip().partition("=")
        fields[name] = value
    t, v1 = fields.get("t", ""), fields.get("v1", "")
    if not t.isdigit() or abs(time.time() - int(t)) > 300:
        return False
    key = base64.urlsafe_b64decode(signing_secret + "=" * (-len(signing_secret) % 4))
    signed = b"chenvoya.v1." + t.encode() + b"." + body
    expected = hmac.new(key, signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, v1)

Checklist

  • Sign the original bytes you received; do not parse and re-encode the JSON first.
  • Compare in constant time, never with plain string equality.
  • Keep your server clock accurate, or the 5-minute window rejects good events.
  • Deduplicate on event_id and keep processed event IDs for at least 7 days and 5 minutes.
  • Keep the signing secret on your servers, like an API key.

Next steps