Skip to main content

Documentation Index

Fetch the complete documentation index at: https://developers.novala.ai/llms.txt

Use this file to discover all available pages before exploring further.

Webhooks let Novala push real-time event notifications to your server whenever something happens in the platform — a lead is created, a deal changes stage, a booking is confirmed, and more. You register an endpoint URL in the dashboard, and Novala sends a POST request to that URL with a JSON payload each time a matching event occurs.

Registering a webhook endpoint

1

Open webhook settings

In the Novala dashboard, navigate to Settings → Webhooks.
2

Add your endpoint

Click Add Endpoint, enter your publicly accessible HTTPS URL, and select the event types you want to receive. To receive all events, select All events or subscribe to *.
3

Save the signing secret

After saving, copy the signing secret shown in the dialog. You will use it to verify incoming requests. It is not shown again after you close the dialog.
Webhook endpoints must be reachable over HTTPS. HTTP endpoints are not supported.

Payload format

Novala sends a POST request with Content-Type: application/json to your endpoint. The request body has this shape:
{
  "id": "evt_01j1abc123xyz",
  "type": "bookings.confirmed",
  "tenantId": "tenant-uuid-here",
  "timestamp": "2024-08-01T09:05:22.341Z",
  "source": "bookings",
  "data": {
    "bookingId": "booking-uuid-001",
    "resourceId": "res-uuid-001",
    "resourceName": "Main Campus Tour",
    "startsAt": "2024-08-01T10:00:00Z",
    "endsAt": "2024-08-01T11:00:00Z",
    "email": "morgan.lee@example.com",
    "firstName": "Morgan",
    "lastName": "Lee"
  }
}
FieldTypeDescription
idstringUnique delivery ID for this event.
typestringEvent type name (for example, pipeline.deal.created).
tenantIdstringUUID of the tenant that generated the event.
timestampstringISO 8601 timestamp of when the event occurred.
sourcestringModule that emitted the event (for example, bookings, pipeline, leads).
dataobjectEvent-specific payload. Shape varies by event type.

Request headers

Novala includes these headers on every webhook delivery:
HeaderDescription
X-Novala-SignatureHMAC-SHA256 signature for verifying the payload (see below).
X-Novala-TimestampISO 8601 timestamp used in the signature computation.
X-Novala-Delivery-IdUUID identifying this specific delivery attempt.
X-Novala-Event-TypeEvent type string (same as type in the body).
User-AgentNovala-Webhooks/1.0

Verifying signatures

Always verify the X-Novala-Signature header before processing a webhook. Verification confirms the request genuinely came from Novala and that the body was not tampered with in transit. The signature is computed as:
HMAC-SHA256(secret, "{timestamp}.{rawBody}")
The result is hex-encoded and prefixed with sha256=.

Verification example

import { createHmac, timingSafeEqual } from 'crypto';

function verifyNovalaWebhook(
  rawBody: Buffer,
  signature: string,
  timestamp: string,
  secret: string
): boolean {
  const message = `${timestamp}.${rawBody.toString('utf8')}`;
  const expected = 'sha256=' + createHmac('sha256', secret)
    .update(message)
    .digest('hex');

  // Use timing-safe comparison to prevent timing attacks
  try {
    return timingSafeEqual(
      Buffer.from(signature, 'utf8'),
      Buffer.from(expected, 'utf8')
    );
  } catch {
    return false;
  }
}

// Express example
app.post('/webhooks/novala', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-novala-signature'] as string;
  const timestamp = req.headers['x-novala-timestamp'] as string;

  if (!verifyNovalaWebhook(req.body, signature, timestamp, process.env.NOVALA_WEBHOOK_SECRET!)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const event = JSON.parse(req.body.toString());
  
  switch (event.type) {
    case 'bookings.confirmed':
      // Handle booking confirmation
      break;
    case 'pipeline.deal.created':
      // Handle new deal
      break;
    case 'leads.lead.created':
      // Handle new lead
      break;
  }

  res.status(200).json({ received: true });
});
Always use a timing-safe comparison function (such as timingSafeEqual in Node.js or hmac.compare_digest in Python) when comparing signatures. Regular string equality is vulnerable to timing attacks.

Responding to webhooks

Your endpoint must return a 2xx HTTP status code within 10 seconds to acknowledge receipt. Any response outside the 200–299 range is treated as a delivery failure and triggers a retry. Return 200 immediately and process the event asynchronously:
TypeScript
app.post('/webhooks/novala', express.raw({ type: 'application/json' }), async (req, res) => {
  // Verify and acknowledge immediately
  if (!verifyNovalaWebhook(req.body, ...)) {
    return res.status(401).end();
  }
  res.status(200).end(); // Acknowledge before processing

  // Process asynchronously
  const event = JSON.parse(req.body.toString());
  await queue.push(event);
});

Retry behavior

If your endpoint returns a non-2xx status or does not respond within 10 seconds, Novala retries the delivery with exponential backoff:
AttemptDelay after failure
11 minute
25 minutes
325 minutes
42 hours
510 hours
After 5 failed attempts the delivery is marked as permanently failed. If an endpoint accumulates 10 consecutive failures it is automatically disabled. You can re-enable it in Settings → Webhooks.

Common event types

Event typeTriggered when
leads.lead.createdA new lead is created.
pipeline.deal.createdA new deal is created.
pipeline.deal.stage-changedA deal moves to a different stage.
pipeline.deal.closed-wonA deal is moved to a closed-won stage.
bookings.confirmedA booking is confirmed.
bookings.cancelledA booking is cancelled.
bookings.resource.createdA new bookable resource is created.
calso.inspection.submittedAn inspection is submitted for review.
contacts.contact.createdA new contact is created.