> ## Documentation Index
> Fetch the complete documentation index at: https://docs.alforse.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Get an HTTP callback when an Alforse record is created, changed, or deleted.

A **webhook** sends an HTTP `POST` to a configured URL when a record in a subscribed table is inserted, updated, or deleted. Use webhooks to react to changes without polling the API.

## Subscribable tables

Each webhook subscribes to one table. The following tables support subscriptions:

| Module | Tables |
| - | - |
| Sales | `salesOrder`, `repairOrder`, `repairOrderLine`, `repairOrderCharge`, `warrantyTerm`, `customerWarrantyTerm`, `warrantyRegistration` |
| Purchasing | `purchaseOrder` |
| Inventory | `receipt`, `shipment` |
| Production | `job` |
| Items | `part`, `material`, `tool`, `consumable`, `service` |
| Quality | `nonConformance` |
| Finance | `fixedAsset` |
| Users | `employee` (update only) |

The **Table** list in the webhook form shows exactly the tables your deployment accepts. A table only offers the operations it publishes (for example `receipt` offers insert and delete; `employee` offers update).

Select at least one trigger operation: **insert**, **update**, or **delete**.

## The payload

Alforse sends a JSON body to the configured URL. `type` is `INSERT`, `UPDATE`, or `DELETE`, `table` names the table, `companyId` the company, and `record` contains the affected record.

```json theme={null}
{
  "type": "UPDATE",
  "table": "salesOrder",
  "companyId": "abc123",
  "record": { "id": "...", "salesOrderId": "SO000042", "status": "To Ship and Invoice" }
}
```

Alforse sets `Content-Type: application/json` and sends these headers (see [Verifying deliveries](#verifying-deliveries)): `x-omeriva-webhook-signature`, `x-omeriva-webhook-timestamp`, `x-omeriva-webhook-id` (the webhook's id), and `x-omeriva-webhook-event` (the event id).

The `x-omeriva-webhook-event` header identifies one change. It remains stable across delivery retries and differs for each new change, including repeated updates to the same record. Use it as the idempotency key.

## Configuration

Create and manage webhooks under **Settings → Webhooks**. A webhook has:

* **Name:** a label, unique within your company.
* **Table:** the one table to watch.
* **URL:** where the `POST` is sent; must be an `http` or `https` URL that resolves to a public address. Alforse refuses to deliver to loopback, private-network, link-local and cloud metadata addresses.
* **Triggers:** any of insert, update, delete (at least one).
* **Active:** webhooks are on by default; switch off to pause delivery.

The list shows a success and error count for each webhook, based on delivery attempts.

## Delivery & reliability

Webhook delivery is asynchronous and normally occurs shortly after the change.

Delivery is **at least once**. If the endpoint does not return a success status within 10 seconds, or returns `5xx` or `429`, Alforse retries with exponential backoff (up to 8 attempts) before marking the change as failed. Other `4xx` responses are not retried. Design for these conditions:

* **Handle duplicates.** A request can be retried after your service has processed it. De-duplicate on the event id header, not `type` and `record.id`, because separate changes to the same record share the latter values.
* **Reconcile after outages.** After retries are exhausted, Alforse drops the change. Query the API when complete event processing is required.

Alforse does not follow redirects. A `3xx` response is a failed attempt, so configure the final URL. A delivery to a refused address is not retried.

## Verifying deliveries

Each webhook has its own signing secret, generated when the webhook is created and stored encrypted. Every delivery carries an `x-omeriva-webhook-signature` header of the form `v1=<hex>`, and the time of the attempt in Unix seconds in `x-omeriva-webhook-timestamp`. The signature is the hex HMAC-SHA256 of `` `${timestamp}.${body}` ``, keyed by the secret string, where `body` is the raw request body. To verify a delivery:

1. Read the raw body before you parse it. Parsing and re-serialising JSON changes the bytes.
2. Compute the HMAC and compare it with the value after `v1=` in constant time.
3. Reject a timestamp more than five minutes from your clock, so a captured request cannot be replayed. Each retry is signed again with a new timestamp.

```ts theme={null}
import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyAlforseSignature(
  rawBody: string,
  signatureHeader: string,
  timestampHeader: string,
  secret: string,
  toleranceSeconds = 300
) {
  const timestamp = Number(timestampHeader);
  if (!Number.isFinite(timestamp)) return false;
  if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false;

  const expected = createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");
  const received = signatureHeader.replace(/^v1=/, "");
  return (
    received.length === expected.length &&
    timingSafeEqual(Buffer.from(received), Buffer.from(expected))
  );
}
```

The app does not currently display or roll the signing secret. Ask Alforse support if you need it to verify deliveries.

NOTE

A delivery tells you a record changed. Treat its body as a notification, and fetch the record with an [API key](/docs/building/api-keys.md) before you act on it.

## Related

**Integrations**

Prefer a packaged connector? See the first-party integrations.

[Open](/docs/integrations.md)

**API keys**

Pull the full record from the Alforse API after a webhook tells you it changed.

[Open](/docs/building/api-keys.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.