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)

Event Type
Description

subscription.created

New subscription created

subscription.updated

Subscription details changed

transaction.created

New transaction recorded

transaction.updated

Transaction status changed

Quick setup

  1. In the dashboard, go to Settings → Developer (bottom of the sidebar).

  2. Enter your Your App Webhook URL.

  3. Click Save (bottom-right).

  4. 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 with application/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 or X-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