Skip to main content
🔗

Webhooks

Receive real-time notifications when events happen in Holded.

⚡ Real-time🔒 Signed payloads🔄 Auto-retry🎯 Event filtering

Overview

Webhooks allow you to receive HTTP POST notifications when events occur in your Holded account, such as a new invoice being created or a payment being received.

Setting up webhooks

Configure webhooks in your Holded dashboard under Settings > Webhooks. You'll need to provide:

  • A publicly accessible HTTPS URL
  • The events you want to subscribe to

Common headers

Every webhook request includes these headers:

x-holded-webhook-eventstring

Event name, e.g. invoice.create

x-holded-webhook-datestring · ISO 8601

When the event was sent, e.g. 2026-06-18T11:10:35Z

x-holded-webhook-signaturestring

HMAC-SHA256 signature with the sha256= prefix (see below)

x-holded-webhook-idstring

Unique event identifier; use it as an idempotency key

x-holded-webhook-account-idstring

Identifier of the Holded account that triggered the event

x-holded-webhook-versionstring

Webhook system version (e.g. v1)

Available events

Webhooks are available for the following event groups. Click a group to see its events and payload details.

Verifying webhooks

Each webhook request includes a signature header (x-holded-webhook-signature) that you should verify to confirm the request came from Holded. The signature is an HMAC-SHA256 hex digest of the raw request body, signed with your webhook secret and prefixed with sha256=.

javascript
const crypto = require('crypto');

// Use the raw request body, not the parsed JSON object
app.post('/holded-webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const secret = process.env.HOLDED_WEBHOOK_SECRET; // whsec_...

  // The header arrives with a "sha256=" prefix; strip it before comparing
  const received = (req.header('x-holded-webhook-signature') || '').replace(/^sha256=/, '');

  const expected = crypto.createHmac('sha256', secret).update(req.body).digest('hex');

  const valid = received.length === expected.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));
  if (!valid) return res.status(401).send('Invalid signature');

  const event = req.header('x-holded-webhook-event');
  const data = JSON.parse(req.body.toString('utf8'));

  // ... process the event and acknowledge
  res.sendStatus(200);
});

Expected response

Your endpoint should return any 2xx status code to acknowledge receipt. If we don't receive a 2xx response, we will retry the delivery with exponential backoff.