Webhooks
Receive real-time notifications about subscription and transaction events so your application stays in sync with payment status.
Zellify uses webhooks to notify your application about key billing events. We handle provider webhooks on your behalf, normalize them, and then send simplified events to your app.
What you receive (event types)
subscription.created
New subscription created
subscription.updated
Subscription details changed
transaction.created
New transaction recorded
transaction.updated
Transaction status changed
Quick setup
In the dashboard, go to
Settings → Developer
(bottom of the sidebar).Enter your
Your App Webhook URL
.Click
Save
(bottom-right).Click
Test
next to the URL input to send a sample event to your endpoint.
Example test request your app will receive:
POST CONTENT:
{"meta":{"occured_at":"2025-08-15T06:48:51.171Z","event_type":"webhook.test","event_id":"test_68a6fe40e1809792"},"data":{"message":"This is a test webhook payload from Zellify","organizationId":"t6barvdv-0","timestamp":"2025-08-15T06:48:51.172Z"}}
HEADERS:
Accept: */*
Content-Type: application/json
User-Agent: Zellify-Buddy Webhooks v1
X-Zellify-Signature: 25afa186f8adff60c8afeaccbd845b458facb4de2c0afa71692ae6094e0e6826
X-Zellify-Delivery: c7d12b89f97e8642accf5d6cf52dc651
Content-Length: 247
Accept-Encoding: gzip, compress, deflate, br
Host: api.webhookinbox.com
Your App Webhook Secret
In Settings → Developer
, copy Your App Webhook Secret
. Use this secret to verify that each webhook truly originates from Zellify.
Compute an HMAC-SHA256 of the raw request body using your secret.
Compare it to the
X-Zellify-Signature
header using a timing-safe comparison.Reject requests with missing/invalid signatures.
Minimal Express-style example:
import crypto from 'crypto';
function verifySignature(signature, rawBody, secret) {
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const a = Buffer.from(signature || '', 'utf8');
const b = Buffer.from(expected, 'utf8');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// Note express.raw to access the raw body bytes
app.post('/webhooks/zellify', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.header('X-Zellify-Signature') || '';
const deliveryId = req.header('X-Zellify-Delivery') || '';
const secret = process.env.ZELLIFY_WEBHOOK_SECRET;
if (!verifySignature(signature, req.body, secret)) {
return res.status(401).send('Unauthorized');
}
const event = JSON.parse(req.body.toString('utf8'));
// Use event.meta.event_id or X-Zellify-Delivery for idempotency
enqueueForAsyncProcessing(event, deliveryId);
res.status(200).send('OK');
});
Minimal endpoint requirements
Public HTTPS URL that accepts
POST
withapplication/json
.Respond with
2xx
within 10 seconds; process work asynchronously.Verify
X-Zellify-Signature
using your secret and the raw request body.Idempotency: de-duplicate using
meta.event_id
orX-Zellify-Delivery
.Log events for troubleshooting; avoid logging secrets.
Best practices (brief)
Only pass validation if the signature is valid.
Keep your secret in environment variables; rotate periodically.
Treat webhooks as untrusted input until verified.
Design for possible retries and duplicate deliveries by making your processing idempotent.
That’s it for the core concepts. This page intentionally stays high-level; we’ll provide a deeper, language-specific guide elsewhere.
Last updated