> ## 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.

# Receive real-time event notifications with webhooks

> Push real-time event notifications to your server. Add webhook endpoints, select event types, and verify deliveries from Settings → Webhooks.

Webhooks let Novala send a notification to your server whenever something happens in your account — a new inspection is completed, an invoice is paid, or a work order is created. Instead of polling the Novala API for changes, your system receives an HTTP POST request with event details immediately when the event fires.

## Add a webhook endpoint

Navigate to **Settings → Webhooks**, then click **Add Endpoint**.

<Steps>
  <Step title="Enter your endpoint URL">
    Provide the HTTPS URL where Novala should send events. The URL must be publicly accessible. During development, a tunneling tool like ngrok can expose a local server.
  </Step>

  <Step title="Add a description (optional)">
    Add a short label to help you identify this endpoint later (for example, "Acumatica sync" or "Slack alerts").
  </Step>

  <Step title="Choose which events to receive">
    By default, **Subscribe to all events** is checked, which means your endpoint receives every event type Novala emits. Uncheck this to select specific event types from the list.
  </Step>

  <Step title="Click Create">
    Novala registers the endpoint and begins delivering matching events immediately.
  </Step>
</Steps>

## Event types

Events are grouped by module. The event type is included in every payload as the `type` field.

<AccordionGroup>
  <Accordion title="Contacts">
    | Event type                 | When it fires          |
    | -------------------------- | ---------------------- |
    | `contacts.company.created` | A new company is added |
    | `contacts.contact.created` | A new contact is added |
  </Accordion>

  <Accordion title="Inspections">
    | Event type                   | When it fires                              |
    | ---------------------------- | ------------------------------------------ |
    | `calso.inspection.completed` | An inspection is marked complete           |
    | `calso.report.generated`     | An inspection report is generated          |
    | `calso.finding.created`      | A new finding is recorded on an inspection |
  </Accordion>

  <Accordion title="Invoicing">
    | Event type                  | When it fires                         |
    | --------------------------- | ------------------------------------- |
    | `invoicing.invoice.paid`    | An invoice is marked as paid          |
    | `invoicing.invoice.overdue` | An invoice passes its due date unpaid |
  </Accordion>

  <Accordion title="Leads">
    | Event type             | When it fires                            |
    | ---------------------- | ---------------------------------------- |
    | `leads.lead.created`   | A new lead is added                      |
    | `leads.lead.converted` | A lead is converted to a deal or contact |
  </Accordion>

  <Accordion title="Work orders">
    | Event type             | When it fires                   |
    | ---------------------- | ------------------------------- |
    | `work-order.created`   | A new work order is created     |
    | `work-order.completed` | A work order is marked complete |
  </Accordion>

  <Accordion title="Bookings">
    | Event type           | When it fires          |
    | -------------------- | ---------------------- |
    | `bookings.confirmed` | A booking is confirmed |
    | `bookings.cancelled` | A booking is cancelled |
  </Accordion>

  <Accordion title="Workflows">
    | Event type                      | When it fires                    |
    | ------------------------------- | -------------------------------- |
    | `workflows.execution.completed` | A workflow finishes successfully |
    | `workflows.execution.failed`    | A workflow encounters an error   |
  </Accordion>
</AccordionGroup>

## Payload format

Every Novala webhook delivery is an HTTPS POST with a `Content-Type: application/json` body. The top-level structure is the same for all event types:

```json theme={null}
{
  "id": "evt_01j9z2k3m4n5p6q7r8s9t0u1v2",
  "type": "calso.inspection.completed",
  "tenantId": "ten_01j9z2k3m4n5p6q7r8s9",
  "timestamp": "2026-05-02T14:23:45.123Z",
  "source": "calso",
  "data": {
    "inspectionId": "ins_01j9z2k3m4n5p6q7",
    "equipmentId": "eqp_01j8x1k2m3n4p5q6",
    "status": "completed",
    "completedAt": "2026-05-02T14:23:44.000Z",
    "technicianId": "usr_01j7w0j1l2m3o4p5",
    "findingCount": 3,
    "reportUrl": "https://app.novala.ai/inspections/ins_01j9z2k3m4n5p6q7/report"
  }
}
```

| Field       | Description                                          |
| ----------- | ---------------------------------------------------- |
| `id`        | Unique ID for this event                             |
| `type`      | The event type string                                |
| `tenantId`  | Your Novala organization ID                          |
| `timestamp` | ISO 8601 timestamp when the event fired              |
| `source`    | The module that emitted the event                    |
| `data`      | Event-specific payload — contents vary by event type |

## Verify webhook signatures

Every delivery includes three headers you can use to verify that the request came from Novala and has not been tampered with:

| Header                 | Description                                |
| ---------------------- | ------------------------------------------ |
| `X-Novala-Signature`   | HMAC-SHA256 signature of the payload       |
| `X-Novala-Timestamp`   | ISO 8601 timestamp of the delivery attempt |
| `X-Novala-Delivery-Id` | Unique ID for this delivery attempt        |

The signature is computed as `sha256=HMAC-SHA256(timestamp + "." + raw_body, endpoint_secret)`.

To verify a delivery:

```javascript theme={null}
const crypto = require("crypto");

function verifySignature(rawBody, timestamp, signature, secret) {
  const message = `${timestamp}.${rawBody}`;
  const expected = "sha256=" + crypto
    .createHmac("sha256", secret)
    .update(message)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}
```

<Warning>
  Always verify the signature before processing a webhook payload. Reject any request where the signature does not match.
</Warning>

## Retry behavior

If your endpoint returns a non-2xx response or does not respond within 10 seconds, Novala retries the delivery automatically with exponential backoff:

| Attempt   | Delay after failure |
| --------- | ------------------- |
| 1st retry | 1 minute            |
| 2nd retry | 5 minutes           |
| 3rd retry | 25 minutes          |
| 4th retry | 2 hours             |
| 5th retry | 10 hours            |

After 5 failed attempts, the delivery is marked as permanently failed. If an endpoint accumulates 10 consecutive failures, Novala automatically disables it to protect your system from continued errors.

<Tip>
  Return an HTTP 200 response as quickly as possible — before you process the event. Use a queue or background worker to handle the business logic so slow processing doesn't cause timeouts.
</Tip>

## Monitor deliveries

Click the **eye** icon next to any endpoint to open its delivery log. Each entry shows the event type, status, HTTP response code, attempt number, timestamp, and any error message.

From the delivery log, you can click **Replay** on any delivery to resend it to the endpoint immediately, regardless of its current status. Replaying follows the same signature and retry logic as original deliveries.

## Test an endpoint

Click the **test tube** icon next to any endpoint to send a test event. Novala delivers a sample payload and shows you the HTTP response code. Use this to confirm your endpoint is reachable and processing signatures correctly before you go live.

## Endpoint status

| Status            | Meaning                                                                                             |
| ----------------- | --------------------------------------------------------------------------------------------------- |
| **Active**        | Receiving events normally                                                                           |
| **Failing**       | Recent deliveries have failed; Novala is retrying                                                   |
| **Auto-disabled** | Disabled after 10 consecutive failures; re-enable from the endpoint list after fixing your receiver |
| **Inactive**      | Manually disabled                                                                                   |
