Skip to main content
The AlgoVoi gateway signs every outbound webhook with an X-AlgoVoi-Signature header containing a UNIX timestamp and two HMAC components. algovoi-webhook-verifier validates the header, enforces replay protection, and returns the parsed event — with no runtime dependency on AlgoVoi infrastructure.
pip install algovoi-webhook-verifier
npm install @algovoi/webhook-verifier

Verification steps

StepCheck
1X-AlgoVoi-Signature header present
2Header matches t=<unix>,v1=<sha256hex>[,v2=<sha384hex>]
3Timestamp within tolerance window (default 300 s)
4v1 = HMAC-SHA256(secret, "{ts}." + raw_body)
5v2 = HMAC-SHA384(HKDF-SHA256(secret, salt, info), "{ts}." + raw_body) — validated when present
6Body is valid JSON object
7type field is a known event type
The v2 component uses HKDF-SHA256 key derivation with salt=b"algovoi-webhook-v2-pqc" and info=b"hmac-sha384-outbound", length 48 bytes.

Quick start

from algovoi_webhook_verifier import verify_webhook, WebhookVerificationError

def handle_webhook(raw_body: bytes, signature_header: str, secret: str):
    try:
        event = verify_webhook(
            payload=raw_body,
            secret=secret,
            signature_header=signature_header,
        )
    except WebhookVerificationError as e:
        # e.code is one of the six typed error codes
        return {"error": e.code}, 400

    if event["type"] == "payment.confirmed":
        data = event["data"]
        print(f"Payment confirmed: {data['resource_id']} on {data['chain']}")
    return {"ok": True}, 200

Framework integration

from flask import Flask, request
from algovoi_webhook_verifier import verify_webhook, WebhookVerificationError
import os

app = Flask(__name__)

@app.route("/webhook", methods=["POST"])
def webhook():
    try:
        event = verify_webhook(
            payload=request.get_data(),
            secret=os.environ["ALGOVOI_WEBHOOK_SECRET"],
            signature_header=request.headers.get("X-AlgoVoi-Signature", ""),
        )
    except WebhookVerificationError as e:
        return {"error": e.code}, 400

    # process event["type"]
    return {"received": True}, 200

Error codes

CodeCauseHTTP suggestion
MISSING_SIGNATUREHeader absent or blank400
MALFORMED_SIGNATUREHeader does not match format400
STALE_SIGNATURETimestamp outside tolerance window400
INVALID_SIGNATUREHMAC mismatch — tampered body or wrong secret401
INVALID_PAYLOADBody is not a valid JSON object400
UNKNOWN_EVENT_TYPEtype field not in known set400

API reference

Python

verify_webhook(
    *,
    payload: bytes,
    secret: str,
    signature_header: str,
    tolerance: int = 300,      # seconds; 0 disables staleness check
    require_v2: bool = False,  # require v2 component present and valid
) -> dict
Raises WebhookVerificationError(code, message) on any failure. .code is one of the six ErrorCode literals. .message is a human-readable description.

TypeScript

verifyWebhook(options: VerifyOptions): WebhookEvent

interface VerifyOptions {
  payload: Buffer | Uint8Array | string;
  secret: string;
  signatureHeader: string;
  tolerance?: number;    // default 300; 0 to disable
  requireV2?: boolean;   // default false
}
Throws WebhookVerificationError with .code (typed ErrorCode) and .message.

Webhook event shape

{
  "id": "evt_01abc...",
  "type": "payment.confirmed",
  "created": 1748000000,
  "api_version": "2024-01-01",
  "data": {
    "tenant_label": "my-shop",
    "resource_id": "pay_xyz...",
    "chain": "base",
    "asset": {
      "id": "usdc",
      "label": "USDC",
      "decimals": 6
    },
    "amount_microunits": 1000000,
    "amount_pretty": "1.00 USDC",
    "tx_id": "0x...",
    "payer_address": "0x...",
    "payment_link_token": "tok_...",
    "payment_link_label": "Checkout"
  }
}

Supported event types

TypeDescription
payment.confirmedOn-chain payment confirmed by the AlgoVoi facilitator
Additional event types will be added in future versions with full vector coverage.

Cross-validation vectors

13 fixtures in vectors/valid/ (5) and vectors/invalid/ (8). Each vector is self-contained — it embeds the secret, raw body, and header so any language implementation can verify itself against the same corpus.
VectorError code
v01_payment_confirmed_v1v2— (valid)
v02_payment_confirmed_v1_only— (valid)
v03_different_secret— (valid)
v04_minimal_payload— (valid)
v05_unicode_payload— (valid)
i01_missing_signatureMISSING_SIGNATURE
i02_malformed_signatureMALFORMED_SIGNATURE
i03_stale_signatureSTALE_SIGNATURE
i04_invalid_signature_v1INVALID_SIGNATURE
i05_wrong_secretINVALID_SIGNATURE
i06_tampered_bodyINVALID_SIGNATURE
i07_invalid_payload_not_jsonINVALID_PAYLOAD
i08_unknown_event_typeUNKNOWN_EVENT_TYPE
Regenerate all vectors:
python vectors/generate_vectors.py

Test results

ImplementationTestsResult
Python unit3434/34
Python vectors1313/13
TypeScript unit3232/32
TypeScript vectors1313/13
Python 47/47 · TypeScript 45/45

8-language cross-validation

104/104 agreements — all 8 language implementations produce byte-for-byte identical HMAC results and identical error-code verdicts across all 13 vectors.
LanguageVectors
Python13/13
TypeScript13/13
Go13/13
Rust13/13
Java13/13
PHP13/13
.NET13/13
Ruby13/13
Attestation and reproduction commands: _attestations/2026-05-31-8-impl-cross-validation.md

See also