Skip to main content
Zenovay
Free12 minutesIntermediate

Webhooks

Configure outbound webhooks to receive real-time notifications from Zenovay when platform events occur, and learn how Zenovay handles inbound payment webhooks internally.

webhooksapiintegrationnotificationsautomation
Last updated:
Free

Configure outbound webhooks to receive real-time notifications from Zenovay when platform events occur, and learn how Zenovay handles inbound payment webhooks internally.

Outbound Webhooks

Zenovay supports user-configurable outbound webhooks so your services can react to platform events as they happen, instead of polling the API for changes.

Plan availability

Outbound webhooks are available on the Free plan and above.

Creating a webhook

Webhooks are managed under /v1/cli/mutate/webhooks on the Zenovay API. Create one with a POST request:

curl -X POST https://api.zenovay.com/v1/cli/mutate/webhooks \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://yoursite.com/zenovay-hook"}'

The response includes the webhook id and a signing secret. The secret is returned only on creation — store it somewhere safe immediately. If you lose it, rotate the secret (see below); the original cannot be retrieved.

Listing webhooks

curl https://api.zenovay.com/v1/cli/mutate/webhooks \
  -H "Authorization: Bearer YOUR_TOKEN"

Verifying webhook signatures

Every webhook delivery is HMAC-signed using the signing secret from creation. Verify the signature on your endpoint before trusting the payload, and use a constant-time comparison so the verification is not vulnerable to timing attacks:

import crypto from 'crypto';

function verifyZenovayWebhook(rawBody, signatureHeader, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signatureHeader)
  );
}

Always validate the raw request body (the exact bytes received), not a re-serialized version, so the signature you compute matches the one Zenovay computed.

The exact header name that carries the signature is documented in the REST API Reference.

Testing a webhook

Send a synthetic test delivery to confirm your endpoint is wired up correctly. The response includes the result of the delivery attempt (HTTP status from your endpoint, response body, and timing):

curl -X POST https://api.zenovay.com/v1/cli/mutate/webhooks/{id}/test \
  -H "Authorization: Bearer YOUR_TOKEN"

Rotating the signing secret

If a secret leaks or you want to rotate periodically, rotate it. The new secret is returned only once in the response:

curl -X POST https://api.zenovay.com/v1/cli/mutate/webhooks/{id}/rotate \
  -H "Authorization: Bearer YOUR_TOKEN"

After rotation, update your endpoint to verify with the new secret. Old signatures stop validating immediately.

Revoking a webhook

curl -X DELETE https://api.zenovay.com/v1/cli/mutate/webhooks/{id} \
  -H "Authorization: Bearer YOUR_TOKEN"

Revoked webhooks stop receiving deliveries immediately. Revocation cannot be undone.

Best practices

  • Reply with a 2xx as fast as possible. Acknowledge first, then process the event in a background job.
  • Be idempotent. Network retries can cause the same event to arrive more than once — key your handler off the event id, not the receive time.
  • Verify before trusting. Reject any request whose HMAC does not validate, and reject requests whose timestamp is far outside an acceptable window.
  • Rotate secrets if anyone outside the team has had access to your webhook configuration or logs.

Inbound Payment Webhooks

Zenovay also processes inbound webhooks from external services to manage subscriptions and infrastructure. These run inside the Zenovay platform and require no configuration from you.

Inbound webhook sources

SourcePurpose
StripeSubscription management, payment processing, checkout completion
Uptime monitoringHealth-check triggers from external monitoring services

Polling alternatives

If outbound webhooks are not a fit for your use case (for example, you need point-in-time aggregates rather than an event stream), you can still build real-time integrations by polling the External API.

Polling with the External API

const API_KEY = process.env.ZENOVAY_API_KEY;
const WEBSITE_ID = process.env.ZENOVAY_WEBSITE_ID;

async function checkAnalytics() {
  const response = await fetch(
    `https://api.zenovay.com/api/external/v1/analytics/${WEBSITE_ID}?timeRange=24h`,
    {
      headers: { 'X-API-Key': API_KEY }
    }
  );

  const data = await response.json();

  if (data.totalVisitors > threshold) {
    await sendSlackNotification(data);
  }
}

// Poll every 5 minutes
setInterval(checkAnalytics, 5 * 60 * 1000);

Live visitor count

Use the public live endpoint to check current visitor count without an API key:

async function getLiveVisitors(trackingCode) {
  const response = await fetch(
    `https://api.zenovay.com/live/${trackingCode}`
  );
  return await response.json();
}

Slack integration example

Build a scheduled report that posts to Slack:

async function sendDailyReport() {
  const response = await fetch(
    `https://api.zenovay.com/api/external/v1/analytics/${WEBSITE_ID}?timeRange=24h`,
    {
      headers: { 'X-API-Key': API_KEY }
    }
  );

  const data = await response.json();

  await fetch(SLACK_WEBHOOK_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      text: `Daily Analytics Report:\n` +
            `Visitors: ${data.totalVisitors}\n` +
            `Page Views: ${data.totalPageViews}\n` +
            `Bounce Rate: ${data.bounceRate}%`
    })
  });
}

CRM integration example

Sync analytics data to your CRM on a schedule:

async function syncToCRM() {
  const response = await fetch(
    `https://api.zenovay.com/api/external/v1/analytics/${WEBSITE_ID}/visitors`,
    {
      headers: { 'X-API-Key': API_KEY }
    }
  );

  const visitors = await response.json();

  for (const visitor of visitors.data) {
    await crm.contacts.update({
      country: visitor.country,
      last_seen: visitor.last_seen,
      page_count: visitor.pageviews
    });
  }
}

Rate Limits for Polling

When building polling integrations, respect the External API rate limits:

PlanRequests/MinuteMonthly Limit
Free101,000
Pro3010,000
Scale60100,000
Enterprise1201,000,000

Polling best practices

  • Cache API responses locally to reduce request frequency
  • Use appropriate polling intervals (5 minutes minimum recommended)
  • Monitor the X-RateLimit-Remaining header to avoid hitting limits
  • Implement exponential backoff if you receive 429 responses

Next Steps

Was this article helpful?