With the webhook safeguards release, HOST Pay no longer sends an unsigned
webhook when signing fails. A missing, inactive or unreadable secret is recorded
as a delivery error before HTTP is attempted. This rule applies to automatic and
manual delivery, in both live and sandbox mode.Use the raw webhook signing secret returned when you create or rotate the
subscription. This is separate from your API authentication secret. Do not use a
stored hash or masked preview as the HMAC key, and do not hash the raw secret first.If the Delivery Log reports a signing error, contact your HOST Pay operator to
check the encryption configuration. If rotation is necessary, save the new secret
and update your receiver immediately; rotation changes the secret used for future
attempts, including retries of older deliveries. There is no timed dual-signing
grace period. Coordinate the change without disabling signature verification.See the upgrade notice
for endpoint requirements. Requests to live endpoints verify the TLS certificate
and hostname; self-signed or mismatched certificates will fail delivery.
The official SDKs implement all of the steps above — constant-time
comparison and replay protection included. Pass the raw request body and
headers straight from your framework:
from fastapi import FastAPI, HTTPException, Requestfrom hostpay import HostPay, SignatureVerificationErrorapp = FastAPI()client = HostPay(api_key="YOUR_API_KEY", secret_key="YOUR_SECRET_KEY")@app.post("/webhooks/hostpay")async def hostpay_webhook(request: Request): try: event = client.webhooks.construct_event( payload=await request.body(), # raw bytes, not request.json() headers=request.headers, secret=WEBHOOK_SIGNING_SECRET, ) except SignatureVerificationError: raise HTTPException(status_code=400, detail="Invalid signature") if event.event == "deposit.completed": ... # handle it return {"ok": True}
import express from "express";import { HostPay, SignatureVerificationError } from "@hostpay/sdk";const client = new HostPay({ apiKey: "YOUR_API_KEY", secretKey: "YOUR_SECRET_KEY" });app.post("/webhooks/hostpay", express.raw({ type: "*/*" }), (req, res) => { try { const event = client.webhooks.constructEvent( req.body, // raw body — use express.raw, not express.json req.headers, WEBHOOK_SIGNING_SECRET, ); if (event.event === "deposit.completed") { // handle it } res.sendStatus(200); } catch (err) { if (err instanceof SignatureVerificationError) return res.sendStatus(400); throw err; }});
Deliveries older than 5 minutes are rejected by default (pass a custom
tolerance in seconds to change this).