Agent skill

Swipelux Webhooks Skill

Receive and verify Swipelux webhooks (stablecoin and fiat on/off-ramp, accounts and transfers API v3, delivered by Svix). Use when setting up Swipelux webhook handlers, debugging svix-signature verification, or handling events like transfer.state_changed, customer.created, capability.status_changed, and account.status_changed.

Install this skill

npx skills add hookdeck/webhook-skills --skill swipelux-webhooks


Swipelux (swipelux.com) is a stablecoin and fiat on/off-ramp, accounts and transfers platform (API v3). It sends an HTTPS POST with a signed JSON body when customers, capabilities, applications, recipients, destinations, accounts or transfers change. New webhook integrations are delivered by Svix, which follows Standard Webhooks and signs with the svix-* header names: svix-id, svix-timestamp and svix-signature.

The body is a v3 envelope: { "id": "evt_...", "type": "transfer.state_changed", "createdAt": "...", "attempt": 1, "resource": { "type": "transfer", "id": "tr_..." }, "data": { "object": { ... }, "previous": { "state": "processing" } } }. id is the event id (your dedupe key). attempt is not a dedupe key or retry counter. resource locates the object, data.object is its state and data.previous holds the changed fields.

Only new integrations are supported. Hookdeck's configuration alert for this source reads: "Only new Swipelux webhook integrations, delivered by Svix, are supported. Legacy integrations use a different scheme and fail verification." Subscribe with v3 event names (POST /v3/webhooks).

When to Use This Skill

  • How do I receive Swipelux webhooks?
  • How do I verify Swipelux webhook signatures (svix-signature)?
  • How do I handle transfer.state_changed, customer.created or capability.status_changed events?
  • How do I get the Swipelux signing secret (GET /v3/webhooks/portal)?
  • Why is my Swipelux webhook signature verification failing, or rejecting Hookdeck retries?

Verification (core)

Signed content is {svix-id}.{svix-timestamp}.{raw body}. Svix signs it with HMAC-SHA256 using the base64-decoded secret (the part after whsec_), base64-encodes the result, and sends one or more space-separated v1,<signature> entries. Use the official svix library, which compares in constant time. Read the svix-* headers, and when svix-id is absent fall back to the webhook-* headers (webhook-id, webhook-timestamp, webhook-signature), as Hookdeck does.

import { Webhook } from 'svix'; // svix 2.x is ESM-only

const prefix = req.headers['svix-id'] ? 'svix' : 'webhook';
const id = req.headers[`${prefix}-id`];
const timestamp = req.headers[`${prefix}-timestamp`];
const signature = req.headers[`${prefix}-signature`];
// rawBody: the raw Buffer/string, never re-serialized JSON
new Webhook(process.env.SWIPELUX_WEBHOOK_SECRET).verify(rawBody, {
  'svix-id': id, 'svix-timestamp': timestamp, 'svix-signature': signature,
  'webhook-id': id, 'webhook-timestamp': timestamp, 'webhook-signature': signature,
}); // throws on a bad signature or a timestamp outside the window
const event = JSON.parse(rawBody.toString('utf8')); // parse only after verify passes
from svix.webhooks import Webhook, WebhookVerificationError

wh = Webhook(os.environ["SWIPELUX_WEBHOOK_SECRET"])  # "whsec_..."
wh.verify(raw_body, {"svix-id": id, "svix-timestamp": ts, "svix-signature": sig,
                     "webhook-id": id, "webhook-timestamp": ts, "webhook-signature": sig})

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

Each endpoint has its own signing secret. Request an app portal link with GET /v3/webhooks/portal (API key with webhooks:write; the link expires after an hour), open it, and reveal the endpoint's secret. Swipelux doesn't print the secret's form, but Svix secrets start with whsec_. Keep the full value in SWIPELUX_WEBHOOK_SECRET. Handlers fail closed: with the variable unset (or not a whsec_ value) they return 500 instead of accepting anything. A missing header or bad signature gets a 400.

Read the raw body: Swipelux's docs say to "Keep the raw body available until verification completes". With Express use express.raw({ type: '*/*' }) so any content type works and the exact bytes are verified.

Hookdeck retries and replays hit the 5-minute window

The svix library always enforces a fixed 5-minute timestamp window, and it can't be configured. Hookdeck retries and replays resend the original svix-timestamp, so the library rejects any retry or replay that happens more than 5 minutes after the original delivery. If you verify behind Hookdeck (or replay old events), use the manual verifier in references/verification.md, where the window can be dropped or widened. Deduplicate on the envelope id instead of relying on the timestamp.

Common Event Types

12 events are documented (the list may not be exhaustive). The name is type in the JSON body, and the examples handle all of them:

EventTriggered When
customer.created / customer.updated / customer.archivedA customer is created, changed or archived
capability.createdA capability is created for a customer
capability.status_changedA capability's status changes after creation
application.status_changedAn institution application's status changes
recipient.status_changedA recipient becomes active, rejected or archived
destination.status_changedA payout destination's status changes
account.createdAn account is created
account.status_changedAn account's status changes (e.g. ready, suspended, archived)
account.details_changedAn account's details (bank or wallet details, routing) change
transfer.state_changedA v3 transfer changes public state (awaiting_funds, processing, action_required, completed, failed, canceled)

Delivery is at least once: duplicates, delays and reordering are normal. Persist the envelope id, return 2xx, then refetch the resource named by resource.type and resource.id before acting. See references/overview.md for payloads.

Environment Variables

# Signing secret of the webhook endpoint (Svix format, starts with whsec_)
SWIPELUX_WEBHOOK_SECRET=whsec_xxxxx

Local Development

# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 swipelux --path /webhooks/swipelux

Register the tunnel URL with POST /v3/webhooks (see references/setup.md), then trigger a change in sandbox or replay a delivery from the portal.

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 13, 2026

View on GitHub →