Skip to main content

Documentation Index

Fetch the complete documentation index at: https://apidocs.hopnow.io/llms.txt

Use this file to discover all available pages before exploring further.

Signature Verification

HopNow signs every webhook payload using HMAC-SHA256 with your webhook secret. The signature is sent in the X-Webhook-Signature header:
X-Webhook-Signature: sha256=a1b2c3d4...
Always verify webhook signatures before processing events.

Implementation

import hmac
import hashlib
import json
from flask import Flask, request, abort

app = Flask(__name__)
WEBHOOK_SECRET = "your_webhook_secret"

def verify_signature(payload, signature, secret):
    if signature.startswith("sha256="):
        signature = signature[7:]

    expected = hmac.new(
        secret.encode("utf-8"),
        payload.encode("utf-8"),
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(signature, expected)

@app.route("/webhook", methods=["POST"])
def handle_webhook():
    payload = request.get_data(as_text=True)
    signature = request.headers.get("X-Webhook-Signature")

    if not verify_signature(payload, signature, WEBHOOK_SECRET):
        abort(401)

    event = json.loads(payload)
    process_event(event)
    return "", 200

Endpoint Requirements

  • HTTPS only — HTTP URLs are rejected during endpoint creation
  • Respond within 30 seconds — return 2xx quickly, process asynchronously if needed
  • Return 2xx — any non-2xx response triggers a retry

Testing Signatures

Test your verification with known values:
def test_signature():
    secret = "test_secret_123"
    payload = '{"id":"evt_test","type":"account.created"}'

    signature = hmac.new(
        secret.encode("utf-8"),
        payload.encode("utf-8"),
        hashlib.sha256
    ).hexdigest()

    assert verify_signature(payload, f"sha256={signature}", secret)
    assert not verify_signature(payload, "sha256=invalid", secret)