Agent skill
Felloh Webhooks Skill
Receive and verify Felloh webhooks. Use when setting up a Felloh webhook endpoint, debugging X-Signature HMAC-SHA256 verification, or handling transaction status, refund status, booking and user events for UK travel payments.
Install this skill
npx skills add hookdeck/webhook-skills --skill felloh-webhooks
Felloh, payments and trust-account infrastructure for UK travel businesses, sends a JSON webhook to your endpoint when a transaction changes status, a refund changes status, a booking is created or a user is created. Each request is signed: the X-Signature header holds the hex HMAC-SHA256 of the raw body, keyed with the signing key shown when you create the webhook.
When to Use This Skill
- How do I receive Felloh webhooks?
- How do I verify the Felloh
X-Signatureheader? - How do I tell transaction, refund, booking and user deliveries apart? (There is no event-name field)
- Why does my Felloh webhook signature verification fail?
- How do I handle Felloh test deliveries (
X-Felloh-Test)?
Verification (core)
const crypto = require('crypto');
const HEX_SHA256 = /^[0-9a-fA-F]{64}$/;
// rawBody: the Buffer exactly as received. secret: the signing key, used as UTF-8 bytes.
function verifyFellohSignature(rawBody, signature, secret) {
if (!secret || typeof signature !== 'string' || !HEX_SHA256.test(signature)) return false;
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest();
return crypto.timingSafeEqual(Buffer.from(signature, 'hex'), expected); // validated above
}
import hashlib, hmac, re
def verify_felloh_signature(raw_body: bytes, signature: str | None, secret: str) -> bool:
if not secret or not signature or not re.fullmatch(r"[0-9a-fA-F]{64}", signature):
return False
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(signature.lower().encode(), expected.encode())
- Header:
X-Signature. The whole value is the bare hex digest: nosha256=prefix, no list, no timestamp. - Signed content: the raw request body bytes only. Don't parse or re-serialise the JSON first (
express.raw({ type: '*/*' }), Next.jsawait request.arrayBuffer(), FastAPIawait request.body()). - Key: the signing key as UTF-8 bytes, used as-is (not hex- or base64-decoded). It is shown once when you create the webhook.
- Check the format (exactly 64 hex characters) before decoding, then compare in constant time. Hex is case-insensitive.
- Missing, empty, malformed or mismatched signature:
401. - Nothing time-based is signed, so there is no replay window to check. Make handlers idempotent instead.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Event Types
Felloh sends no event-name field or header. The events are chosen when you register the webhook, and you tell deliveries apart by payload shape. The names below are labels used by this skill (and Hookdeck's sample payloads), not values on the wire.
| Event (label) | Payload shape | Retried? |
|---|---|---|
transaction-status | transaction.id plus a string status (for example COMPLETE), amount, currency, booking, provider | Yes: up to 5 times, about a minute apart |
refund-status | authorisation_code, status object (status.id, for example PENDING_AUTHORISATION), transaction object | No |
booking | booking object (id, booking_reference), customer_name, departure_date, return_date | No |
user-create | user object (id), email, first_name, last_name, organisation | No |
Dashboard test deliveries carry X-Felloh-Test: true, are signed with the real key and are never retried.
Important Headers
| Header | Description |
|---|---|
X-Signature | Hex HMAC-SHA256 of the raw body |
X-Felloh-Test | true on dashboard test deliveries |
Delivery
Any non-2xx response is a failure. Timeout is 30 seconds. Only transaction status events are retried; refund, booking and user events are sent once, so return 2xx quickly and process asynchronously. Felloh keeps delivery logs for 30 days.
Environment Variables
FELLOH_WEBHOOK_SECRET=your_signing_key # shown once when you create the webhook
Local Development
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 felloh --path /webhooks/felloh
Use the printed URL as the webhook URL in Felloh.
Reference Materials
- references/overview.md - What Felloh webhooks are, events and payload shapes
- references/setup.md - Register the webhook and get the signing key
- references/verification.md - Signature details, gotchas, debugging 401s
- examples/ - Runnable Express, Next.js and FastAPI handlers with tests