Skip to main content
Webhooks allow Vendaze to notify your application when events occur in the workspace. Instead of polling, your server receives an HTTP POST the moment something changes. Webhooks can be created and managed in two ways:
  • By the Vendaze customer directly in the platform dashboard
  • Via the public API using the webhooks:manage scope

How it works

  1. A webhook is created with a destination URL and the list of events to subscribe to
  2. When a subscribed event fires, Vendaze enqueues the delivery and sends a POST to that URL
  3. Your server processes the payload and returns 2xx within 10 seconds

Managing webhooks via API

Webhooks created via the API are only visible to the OAuth app that created them. They do not appear in the Vendaze dashboard and cannot be managed by other apps, even if they access the same workspace.
EndpointDescription
GET /v1/webhooksList webhooks created by your app
POST /v1/webhooksCreate a webhook
GET /v1/webhooks/:idGet a webhook by ID
PATCH /v1/webhooks/:idUpdate a webhook
DELETE /v1/webhooks/:idDelete a webhook

Authenticated deliveries

When auth_enable: true is sent on webhook creation, Vendaze generates a webhook_secret (format: whsec_...) and includes it in the POST /v1/webhooks response. Store it: it will be used to verify incoming deliveries. Each delivery to your endpoint will include the Webhook-Signature header. See Signature verification for implementation details. Changing auth_enable on an existing webhook affects the secret:
  • false to true: a new webhook_secret is generated and returned
  • true to false: the existing webhook_secret is permanently deleted

Payload

Every delivery is a POST with Content-Type: application/json. The envelope structure is:
FieldTypeDescription
idstringUnique delivery ID. Use for deduplication.
eventstringEvent name, e.g. person.created
versionstringAlways v1
created_atstringISO 8601 UTC timestamp of when the event was dispatched
<entity>objectThe entity data. The key matches the event type: person, company, deal, task, activity, tag, product, pipeline, or member
{
  "id": "event_3f9a1c8b2d...",
  "event": "person.created",
  "version": "v1",
  "created_at": "2026-05-26T14:00:00.000Z",
  "person": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "full_name": "Anna Reed",
    "email": { "1": "anna@acme.com" },
    "created_at": "2026-05-26T14:00:00Z"
  }
}

Signature verification

When auth_enable is true, every delivery includes:
Webhook-Signature: sha256=<hex>
The value is HMAC-SHA256 computed over the raw request body, hex-encoded. Always read the raw bytes before parsing: re-serializing the body may alter whitespace or key order and cause verification to fail. Node.js:
const crypto = require('crypto');

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

// Express: read raw bytes before any JSON parsing
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['webhook-signature'];
  if (!sig || !isValidSignature(req.body, sig, process.env.WEBHOOK_SECRET)) {
    return res.status(401).end();
  }
  const payload = JSON.parse(req.body);
  res.sendStatus(200);
  processEvent(payload); // async, after responding
});
Python:
import hmac, hashlib

def is_valid_signature(raw_body: bytes, received: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(received, expected)
Always use constant-time comparison (timingSafeEqual / hmac.compare_digest). Standard string equality is vulnerable to timing attacks.

Available events

EventTrigger
person.createdA person is created
person.updatedA person is updated
person.deletedA person is deleted
EventTrigger
company.createdA company is created
company.updatedA company is updated
company.deletedA company is deleted
EventTrigger
deal.createdA deal is created
deal.updatedA deal is updated
deal.stage_changedA deal moves to a different stage
deal.wonA deal is marked as won
deal.lostA deal is marked as lost
deal.deletedA deal is deleted
EventTrigger
task.createdA task is created
task.updatedA task is updated
task.completedA task is marked as completed
task.deletedA task is deleted
EventTrigger
activity.createdAn activity is logged
activity.updatedAn activity is updated
activity.deletedAn activity is deleted
EventTrigger
product.createdA product is created
product.updatedA product is updated
product.deletedA product is deleted
EventTrigger
tag.createdA tag is created
tag.updatedA tag is updated
tag.deletedA tag is deleted
EventTrigger
pipeline.createdA pipeline is created
pipeline.updatedA pipeline is updated
pipeline.deletedA pipeline is deleted
EventTrigger
member_invitedA member is invited to the workspace
member_joinedA member joins the workspace
member_removedA member is removed from the workspace
EventTrigger
usage_logs.createdA usage log entry is recorded

Retries and delivery guarantees

If your server does not return 2xx within 10 seconds, Vendaze retries the delivery up to 3 times, each attempt delayed by 15 minutes. After all retries are exhausted, the event is permanently dropped. There is no suspension mechanism: missed deliveries are lost.
AttemptDelay
1 (initial)Immediate
215 minutes after attempt 1
315 minutes after attempt 2
415 minutes after attempt 3
Vendaze delivers with at-least-once semantics. The same event may be delivered more than once due to network issues or retry overlap. Deduplicate using the id field in the envelope.
async function handleWebhook(payload) {
  if (await db.events.findOne({ id: payload.id })) return;
  await db.events.insert({ id: payload.id, processedAt: new Date() });
  await processEvent(payload);
}

Best practices

  • Return 200 immediately and process asynchronously. Any handler that takes more than 10 seconds will trigger a retry.
  • Reject missing or invalid signatures with 401. Never process a delivery from an authenticated endpoint without verifying the signature first.
  • Retain received payloads for at least 30 days to aid debugging and auditing.
  • Return 200 for unrecognized event types. New events will be added over time and silently ignoring them keeps your handler stable across API updates.