Agent skill
Ziina Webhooks Skill
Receive and verify Ziina webhooks. Use when setting up Ziina webhook handlers, debugging X-Hmac-Signature verification, or handling payment_intent.status.updated and refund.status.updated events from the Ziina payments API.
Install this skill
npx skills add hookdeck/webhook-skills --skill ziina-webhooks
Ziina is a UAE payments app with a payment API. Each account has one webhook URL, set with POST /webhook. Ziina sends a POST to it whenever a payment intent or refund changes status.
When to Use This Skill
- How do I receive Ziina webhooks?
- How do I verify the Ziina
X-Hmac-Signatureheader? - How do I handle
payment_andintent.status.updated refund.status.updatedevents? - Why is my Ziina webhook signature verification failing, or why are deliveries unsigned?
Setup note
Set a secret when you register the webhook through the Ziina API. Without one, deliveries are unsigned and fail verification.
Verification (core)
When a secret was given to POST /webhook, Ziina sends X-Hmac-Signature: the bare lowercase hex HMAC-SHA256 of the raw request body, keyed with that secret's UTF-8 bytes. No prefix, and no timestamp or id is signed. This is not Standard Webhooks.
const crypto = require('crypto');
function verifyZiinaWebhook(rawBody, signatureHeader, secret) {
// Validate format first: exactly 64 hex characters
if (!secret || typeof signatureHeader !== 'string' || !/^[0-9a-fA-F]{64}$/.test(signatureHeader)) {
return false;
}
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest(); // rawBody is a Buffer
const received = Buffer.from(signatureHeader, 'hex');
return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}
Reject a missing, empty, malformed or mismatching header with 401. Parse the JSON only after the check.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Event Types
Dispatch on the top-level event field. The new state is in data.status; the event name does not change with the status.
| Event | Triggered When |
|---|---|
payment_ | A payment intent changes status (e.g. completed) |
refund.status.updated | A refund changes status |
Ziina retries a non-OK response 3 times; no timeout or spacing is documented. Return 200 quickly and handle duplicates idempotently.
Environment Variables
ZIINA_WEBHOOK_SECRET=your_chosen_secret # The `secret` you passed to POST /webhook
Local Development
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 ziina --path /webhooks/ziina
Reference Materials
- references/overview.md - What Ziina webhooks are, events, payload
- references/setup.md - Registering the webhook and secret
- references/verification.md - Signature verification details and gotchas