Verify signatures#

This page explains how to verify that a webhook request was sent by ALPHA. Every delivery is an HTTPS POST signed with HMAC-SHA256 (scheme v1). Always verify using the raw request body before you parse JSON.

Important

If verification fails, return 401 or 403 and do not process the body.


Request headers#

Header

Type

Description

webhook-id

string

Event identifier (same as JSON id). Use for idempotency.

webhook-timestamp

integer

Unix time in seconds when this delivery attempt was signed.

webhook-signature

string

Space-separated signature entries. ALPHA sends v1,<base64>.

Endpoint secret#

The secret format is whsec_<base64>. Remove the whsec_ prefix, base64-decode the remainder, and use those bytes as the HMAC key.


How to verify#

Build the signed string from the same request you received:

{webhook-id}.{webhook-timestamp}.{raw_body}

Then:

  1. Compute HMAC-SHA256 over the UTF-8 bytes of that string.

  2. Base64-encode the 32-byte digest.

  3. Compare v1,<base64> to an entry in webhook-signature with a constant-time comparison.

  4. Reject the request if webhook-timestamp is outside a small window (for example ±5 minutes) to limit replay attacks.

Warning

ALPHA serializes JSON with sorted keys and compact separators (",", ":"). Re-serializing a parsed object usually breaks the signature. Always use the raw HTTP body bytes.

Example implementation (Python)#

import base64
import hashlib
import hmac
import time

def verify(headers, raw_body: bytes, secret: str, tolerance: int = 300) -> bool:
    # secret like "whsec_..."
    key = base64.b64decode(secret.removeprefix("whsec_"))
    msg_id = headers["webhook-id"]
    ts = int(headers["webhook-timestamp"])
    if abs(time.time() - ts) > tolerance:
        return False
    signed = f"{msg_id}.{ts}.{raw_body.decode('utf-8')}".encode("utf-8")
    digest = hmac.new(key, signed, hashlib.sha256).digest()
    expected = "v1," + base64.b64encode(digest).decode("ascii")
    candidates = headers["webhook-signature"].split()
    return any(hmac.compare_digest(expected, c) for c in candidates)

See also#

Event types

Event envelope and sample payloads.

Event types
Deliveries

Retry behavior and HTTP response handling.

Deliveries