Agent skill
Yapily Webhooks Skill
Receive and verify Yapily webhooks. Use when setting up Yapily open banking webhook handlers, debugging webhook-signature verification, or handling payment events like single_payment.status.completed and hosted_pis.payment_status.completed.v1.
Install this skill
npx skills add hookdeck/webhook-skills --skill yapily-webhooks
When to Use This Skill
- How do I receive Yapily webhooks?
- How do I verify the Yapily
webhook-signatureheader? - Why is my Yapily webhook signature verification failing?
- How do I handle
single_orpayment.status.completed hosted_events?pis.payment_ status.failed.v1 - How do I rotate a Yapily webhook secret without downtime (
webhook-rotation-signature)?
Verification (core)
Yapily signs the raw request body with HMAC-SHA256, keyed with the webhook secret as a UTF-8 string, and sends the plain lowercase hex digest in webhook-signature. There is no prefix, timestamp or message ID: this is not Standard Webhooks, despite the header name. During a delayed secret reset Yapily also sends webhook-rotation-signature; accept a match on either header. Compare case-insensitively and timing-safe.
const crypto = require('crypto');
function verify(rawBody, signatures, secret) {
const expected = Buffer.from(
crypto.createHmac('sha256', Buffer.from(secret, 'utf-8')).update(rawBody).digest('hex')
);
return signatures.some((sig) => {
if (!sig) return false;
const received = Buffer.from(sig.trim().toLowerCase());
return received.length === expected.length && crypto.timingSafeEqual(received, expected);
});
}
// signatures = [req.headers['webhook-signature'], req.headers['webhook-rotation-signature']]
import hmac, hashlib
def verify(raw_body: bytes, signatures: list, secret: str) -> bool:
expected = hmac.new(secret.encode("utf-8"), raw_body, hashlib.sha256).hexdigest()
return any(hmac.compare_digest(s.strip().lower().encode(), expected.encode()) for s in signatures if s)
In Express, read the body with express.raw({ type: '*/*' }) so every content type reaches the verifier as a Buffer. Never JSON.parse and re-serialise before verifying.
For complete handlers with route wiring, event dispatch, and tests, see:
Payload Shape
{ "id": "<webhook id>", "applicationId": "<app id>", "type": "<event type>", "event": { }, "metadata": { "tracingId": "...", "user": { } } }
event varies by type. metadata.user echoes the metadata you supplied when registering the webhook.
Common Event Types
Subscribe by event category with POST /webhook/events; list the categories available to you with GET /webhook/events/categories. The type values in the documented payloads are:
type | Description |
|---|---|
single_ | Single payment status moved to COMPLETED |
single_ | Single payment status moved to COMPLETED or FAILED |
single_ | Single payment ISO 20022 status changed (can transition several times) |
hosted_ | Hosted payment completed successfully |
hosted_ | Hosted payment failed |
Async data-access completion and other categories also use this envelope; handlers should log and return 200 for types they don't recognise.
Important Headers
| Header | Description |
|---|---|
webhook-signature | Hex HMAC-SHA256 of the raw body |
webhook-rotation-signature | Only present during a delayed secret reset; signed with the other secret |
Environment Variables
YAPILY_WEBHOOK_SECRET=your_webhook_secret # webhookSecret from POST /webhook/events; shown only once
Local Development
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 yapily --path /webhooks/yapily
Reference Materials
- references/overview.md - Yapily webhook concepts, event types, delivery rules
- references/setup.md - Register a webhook and manage the secret
- references/verification.md - Signature verification details and gotchas