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

> 每当订阅表中的记录被插入、更新或删除时，让 Alforse 向您的端点 POST 一条带签名的 JSON 通知。

**Webhook** 会让 Alforse 在订阅表中的记录被插入、更新或删除时，向您选择的 URL 发送一个 HTTP `POST`。开发者使用 Webhook 来响应 Alforse 中的变更，而无需轮询 API。

## Webhook 的工作方式

* 每个 Webhook 监视**一张表**和一种或多种**触发操作**。
* 发生匹配的变更时，Alforse 会将一次投递加入队列并异步发送，通常在片刻之内。
* 每次投递都用属于该 Webhook 的密钥签名，因此您的端点可以证明请求来自 Alforse。
* 请求正文是一条记录已变更的通知。在据此采取行动之前，请使用 [API 密钥](https://docs.alforse.com/zh/docs/building/api-keys.md)获取当前记录。

## 可订阅的表

| 模块 | 表 |
| - | - |
| 销售 | `salesOrder`、`repairOrder`、`repairOrderLine`、`repairOrderCharge`、`warrantyTerm`、`customerWarrantyTerm`、`warrantyRegistration` |
| 采购 | `purchaseOrder` |
| 库存 | `receipt`、`shipment` |
| 生产 | `job` |
| 物料 | `part`、`material`、`tool`、`consumable`、`service` |
| 质量 | `nonConformance` |
| 财务 | `fixedAsset` |
| 用户 | `employee`（仅更新） |

请以 Webhook 表单中的**表**下拉列表为准，了解您的部署接受哪些表。触发选项也取决于表，因为一张表只发布部分操作：`receipt` 发布插入和删除，`employee` 只发布更新。

## 设置 Webhook

前往**设置 → Webhooks** 创建和管理 Webhook。填写以下字段：

| 字段 | 作用 |
| - | - |
| **名称** | Webhook 的标签。在您的公司内必须唯一。 |
| **表** | 要监视的单张表。 |
| **网址** | Alforse 发送 `POST` 的目标地址。必须是解析到公网地址的 `http` 或 `https` URL。 |
| **触发器** | **insert**、**update** 和 **delete** 的任意组合。至少选择一项。 |
| **启用** | 默认开启。关闭它可以暂停投递而不删除 Webhook。 |

Alforse 拒绝向环回地址、私有网络地址、链路本地地址和云元数据地址投递。这可以防止 Webhook 被用来访问内部系统。

Webhook 列表显示每个 Webhook 的成功次数和错误次数，根据投递尝试统计。

## 一次投递包含什么

### 正文

正文是包含四个字段的 JSON：

| 字段 | 含义 |
| - | - |
| `type` | `INSERT`、`UPDATE` 或 `DELETE` |
| `table` | 发生变更的表 |
| `companyId` | 记录所属的公司 |
| `record` | 受影响的记录 |

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

### 标头

| 标头 | 值 |
| - | - |
| `Content-Type` | `application/json` |
| `x-omeriva-webhook-signature` | 签名，格式为 `v1=<hex>`（参见[验证投递](#verifying-deliveries)） |
| `x-omeriva-webhook-timestamp` | 本次尝试的时间，Unix 秒 |
| `x-omeriva-webhook-id` | Webhook 的 ID |
| `x-omeriva-webhook-event` | 事件 ID |

请将事件 ID 用作幂等键。一次变更在其所有重试中保持同一个事件 ID，而每个新的变更都会得到新的事件 ID，包括对您已经收到通知的记录的第二次更新。

## 投递与重试

投递为**至少一次**。当您的端点出现以下情况时，Alforse 会将该次尝试视为失败，并以指数退避方式重试，最多 8 次：

* 未在 10 秒内返回成功状态，或
* 返回 `5xx` 或 `429`。

其他结果不会重试：

* 任何其他 `4xx` 响应都会终止该变更的投递。
* `3xx` 响应算作失败，因为 Alforse 不会跟随重定向。请配置最终的 URL。
* 向被拒绝的地址投递不会重试。

最后一次尝试失败后，Alforse 将该变更标记为失败并丢弃。

### 设计您的端点

* **预期会有重复。** 重试可能在您已处理第一次尝试之后到达。请基于 `x-omeriva-webhook-event` 去重，而不是基于 `type` 加 `record.id`，因为对同一记录的不同变更共享这些值。
* **中断之后进行对账。** 被丢弃的变更不会重放。如果您必须处理每个事件，请在端点停机之后查询 API 来追赶进度。

## 验证投递

创建 Webhook 的同时会生成一个专属于它的签名密钥，Alforse 对其加密保存。`x-omeriva-webhook-signature` 中的签名是 `` `${timestamp}.${body}` `` 的十六进制 HMAC-SHA256，以该密钥字符串为密钥。`timestamp` 是 `x-omeriva-webhook-timestamp` 的值，`body` 是原始请求正文。

验证投递的步骤：

1. 在解析之前捕获原始正文。解析并重新序列化 JSON 会改变字节，使签名失效。
2. 计算 HMAC，并使用恒定时间比较，将其与 `v1=` 之后的值比较。
3. 拒绝与您的时钟相差超过五分钟的任何时间戳，以防捕获的请求被重放。每次重试都会用新的时间戳重新签名，因此合法的重试仍可通过。

```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))
  );
}
```

NOTE

**获取签名密钥**

应用目前不显示也不轮换签名密钥。设置验证时，请向 Alforse 支持团队索取。

## 故障排除

| 症状 | 原因 | 解决办法 |
| - | - | - |
| 您的服务器从未收到任何内容 | URL 解析到环回、私有、链路本地或元数据地址，因此 Alforse 拒绝它且不重试 | 使用可公开访问的 URL。 |
| 尽管服务器正常，每次尝试仍失败 | URL 返回 `3xx` 重定向 | 输入重定向指向的最终 URL。 |
| 同一变更被处理两次 | 在缓慢或失败的响应之后，重试到达 | 基于 `x-omeriva-webhook-event` 去重。 |
| 有效请求的签名校验失败 | 哈希之前已解析了正文 | 对原始正文字节做哈希。 |
| **表**中缺少某张表 | 该表不发布 Webhook 事件 | 订阅相关的表，或轮询 API。 |

## 相关内容

**API 密钥**

在 Webhook 告知记录已变更之后，从 Alforse API 获取完整记录。

[Open](https://docs.alforse.com/zh/docs/building/api-keys.md)

**集成**

更想用打包好的连接器？请参见第一方集成。

[Open](https://docs.alforse.com/zh/docs/integrations.md)


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