Docs

Webhooks

Listen for events from your Zoneless instance so your platform can automatically react to changes — such as when a connected account completes onboarding or a payout is sent.

Zoneless sends webhook events to your platform as HTTP POST requests containing a JSON Event object. This lets you respond to asynchronous events like account status changes, completed payouts, and new transfers without polling the API.

How it works

  • Register a webhook endpoint URL via the Platform Dashboard or the Webhook Endpoints API.
  • Zoneless sends a POST request to your URL whenever a subscribed event occurs.
  • Your handler verifies the signature, processes the event, and returns a 2xx response.

Create a handler

Set up an endpoint that accepts POST requests with a JSON payload. Your handler should:

  • Verify the webhook signature using the Zoneless-Signature header.
  • Handle the event based on its type field.
  • Return a 2xx response quickly, before any complex processing.
Webhook handler
import express from 'express';
import { Zoneless } from '@zoneless/node';

const app = express();
const zoneless = new Zoneless('sk_live_z_YOUR_API_KEY', 'https://api.zoneless.com');
const endpointSecret = 'whsec_z_...';

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['zoneless-signature'];
  let event;

  try {
    event = zoneless.webhooks.constructEvent(req.body, sig, endpointSecret);
  } catch (err) {
    console.log('Webhook signature verification failed.', err.message);
    return res.sendStatus(400);
  }

  switch (event.type) {
    case 'checkout.session.completed':
      const session = event.data.object;
      // Fulfill the order
      break;
    case 'account.updated':
      const account = event.data.object;
      if (account.payouts_enabled) {
        // The connected account has completed onboarding
      }
      break;
    case 'payout.paid':
      const payout = event.data.object;
      // The payout was sent to the wallet
      break;
    case 'transfer.created':
      const transfer = event.data.object;
      // A transfer was created to a connected account
      break;
    default:
      console.log('Unhandled event type', event.type);
  }

  res.json({ received: true });
});

app.listen(4242, () => console.log('Running on port 4242'));

Verify signatures

Zoneless signs every webhook event by including a signature in the Zoneless-Signature header. Always verify this signature to confirm the event was sent by your Zoneless instance and not a third party.

The header contains a timestamp (t) and a signature (v1). The signature is an HMAC-SHA256 hash of {timestamp}.{payload}, using the endpoint's signing secret as the key.

Verify webhook signature
import express from 'express';
import { Zoneless } from '@zoneless/node';

const app = express();
const zoneless = new Zoneless('sk_live_z_YOUR_API_KEY', 'https://api.zoneless.com');

const endpointSecret = 'whsec_z_...';

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['zoneless-signature'];
  let event;

  try {
    event = zoneless.webhooks.constructEvent(req.body, sig, endpointSecret);
  } catch (err) {
    res.status(400).send('Webhook Error: ' + err.message);
    return;
  }

  // Handle the event
  switch (event.type) {
    case 'account.updated':
      const account = event.data.object;
      console.log('Account updated:', account.id);
      break;
    default:
      console.log('Unhandled event type', event.type);
  }

  res.json({ received: true });
});

Event types

When registering a webhook endpoint, specify which event types to subscribe to. Use ["*"] to receive all events. See Types of events for the full list of available event types.

Retries

Zoneless does not currently retry failed webhook deliveries. If your endpoint returns a non-2xx response or is unreachable, the event will not be re-sent. Automatic retries with exponential backoff are a planned future addition.

In the meantime, you can use the Events API to list and retrieve events you may have missed.

Best practices

  • Handle duplicate events. Log processed event IDs and skip duplicates. Use data.object.id along with event.type to identify unique events.
  • Only subscribe to events you need. Listening for all events puts unnecessary load on your server.
  • Process events asynchronously. Push events onto a queue and process them in the background to handle traffic spikes.
  • Use HTTPS in production. Your webhook endpoint must be publicly accessible over HTTPS in live mode.
  • Verify every request. Always verify the Zoneless-Signature header to confirm events originate from your Zoneless instance.