This document explains how to securely verify incoming webhooks from Stay AI using JSON Web Tokens (JWT). Verifying webhooks is critical to ensuring requests are authentic and have not been tampered with or replayed.
Overview
Stay AI signs each webhook request with a JWT, provided in the x-retextion-webhook-token header. This token:
- Is signed using a merchant-specific API key (Creating an API Key in Stay)
- Includes a required
iat(issued at) field - Does not include an
exp(expiration) field
Webhook verification requires:
- Looking up the correct API key using the shop domain.
- Decoding and verifying the JWT using that key.
- Ensuring the
iatis recent (e.g., within the last 10 minutes).
Headers
Webhook requests include the following headers:
Python Implementation (Flask Example)
from flask import Blueprint, request, jsonify
import jwt
import time
# Register Blueprint for webhook verification
webhook_verification_bp = Blueprint('webhook_verification', __name__)
@webhook_verification_bp.route('/webhooks/verify', methods=['POST'])
def handle_webhook_verification():
token = request.headers.get('x-retextion-webhook-token')
shop_domain = request.headers.get('x-retextion-webhook-shop')
api_key = get_api_key_for_shop(shop_domain)
if not token or not api_key:
return jsonify({"error": "Missing token or API key"}), 400
try:
# Decode the JWT and require 'iat'
decoded = jwt.decode(
token,
api_key,
algorithms=["HS256"],
options={"require": ["iat"]}
)
# Check token age (10-minute window)
now = int(time.time())
age = now - decoded['iat']
if age > 600:
return jsonify({"error": "Token too old"}), 401
except jwt.InvalidTokenError as e:
return jsonify({"error": "Invalid token", "details": str(e)}), 401
return jsonify({"status": "Webhook verified"}), 200
def get_api_key_for_shop(shop_domain):
"""
Replace this with your actual API key lookup logic.
This should return the secret key used to sign the JWT for the shop.
"""
return 'STAY_AI_API_KEY'Best Practices
Validate token freshness: The iatclaim is required. Tokens older than 10 minutes should be rejected.
Never trust unsigned payloads: Always verify the JWT signature before using any token data.
