Agent skill
Dwolla Webhooks Skill
Receive and verify Dwolla webhooks. Use when setting up Dwolla webhook handlers, debugging X-Request-Signature-SHA-256 verification, or handling ACH payment events like customer_created, customer_transfer_completed, and customer_funding_source_verified.
Install this skill
npx skills add hookdeck/webhook-skills --skill dwolla-webhooks
When to Use This Skill
- How do I receive Dwolla webhooks?
- How do I verify the
X-Request-Signature-SHA-256header on a Dwolla webhook? - How do I handle
customer_createdorcustomer_events?transfer_ completed - Why is my Dwolla webhook signature verification failing?
- How do I deduplicate Dwolla webhooks by event ID?
Verification (core)
Dwolla computes an HMAC-SHA256 of the raw request body, keyed with the secret you supplied when creating the webhook subscription (used as a raw string), and sends the lowercase hex digest in X-Request-Signature-SHA-256. There is no timestamp and no prefix. Dwolla has no webhook-verification SDK helper, so verify manually. IP allowlisting is not supported because Dwolla's IPs are dynamic.
Node:
const crypto = require('crypto');
function verify(rawBody, signature, secret) {
if (!signature) return false;
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
try {
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
} catch {
return false; // different lengths
}
}
Python:
import hmac, hashlib
def verify(raw_body: bytes, signature: str, secret: str) -> bool:
if not signature:
return False
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(signature, expected)
Never re-encode the JSON before verifying; Dwolla's docs warn that re-encoded bodies produce a different signature.
For complete handlers with route wiring, event dispatch, and tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Common Event Types
The event type is in the payload's topic field (also sent in the X-Dwolla-Topic header).
| Topic | Description |
|---|---|
customer_created | A Customer was created |
customer_verified | A Customer was verified |
customer_ | A funding source was added to a Customer |
customer_ | A funding source was verified |
customer_ | A transfer was created for a Customer |
customer_ | A Customer transfer succeeded |
customer_transfer_failed | A Customer transfer failed |
customer_ | A bank transfer for a Customer succeeded |
For the full topic list, see Dwolla Webhook Events.
Payload and Headers
Payload: id (event ID), resourceId, topic, timestamp, and _links (self, account, resource, customer). Fetch the resource through _links.resource.href for current state.
| Header | Description |
|---|---|
X-Request-Signature-SHA-256 | Hex HMAC-SHA256 of the raw body |
X-Dwolla-Topic | Event topic (e.g. customer_created) |
Respond with a 2xx within 10 seconds. Dwolla retries up to 8 times over 72 hours and pauses the subscription after 400 consecutive failures (with 24 hours since the last success). Deduplicate on the event id, not resourceId or topic, and return 200 for duplicates.
Environment Variables
DWOLLA_WEBHOOK_SECRET=your_subscription_secret # The "secret" sent when creating the webhook subscription
Local Development
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 dwolla --path /webhooks/dwolla
Reference Materials
- references/overview.md - Dwolla webhook concepts and events
- references/setup.md - Creating a webhook subscription
- references/verification.md - Signature verification details