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

> 購読しているテーブルのレコードが挿入、更新、削除されるたびに、署名付きの JSON 通知を Alforse からお客様のエンドポイントへ POST します。

**Webhook**を設定すると、購読しているテーブルのレコードが挿入、更新、削除されるたびに、Alforse が指定した URL へ HTTP `POST` を送信します。開発者は Webhook を使うことで、API をポーリングせずに Alforse の変更に対応できます。

## Webhook の仕組み

* 各 Webhook は、**1 つのテーブル**と 1 つ以上の**トリガー操作**を監視します。
* 条件に一致する変更が発生すると、Alforse は配信をキューに入れ、非同期で送信します。通常は数瞬で届きます。
* 各配信には、その Webhook 固有のシークレットで署名が付くため、エンドポイント側でリクエストが Alforse から送られたことを確認できます。
* 本文は、レコードが変更されたことの通知です。対応する前に、[APIキー](https://docs.alforse.com/ja/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 のラベル。会社内で一意である必要があります。 |
| **テーブル** | 監視する 1 つのテーブル。 |
| **URL** | Alforse が `POST` を送信する先。公開アドレスに解決される `http` または `https` の URL である必要があります。 |
| **トリガー** | **insert**、**update**、**delete** の任意の組み合わせ。少なくとも 1 つ選択します。 |
| **有効** | デフォルトでオン。オフにすると、Webhook を削除せずに配信を一時停止できます。 |

Alforse は、ループバック、プライベートネットワーク、リンクローカル、クラウドのメタデータアドレスへの配信を拒否します。これにより、Webhook が内部システムへのアクセスに悪用されるのを防ぎます。

Webhook の一覧には、各 Webhook の成功数とエラー数が表示されます。これは配信の試行回数から集計されます。

## 配信の内容

### 本文

本文は 4 つのフィールドを持つ 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 を冪等キーとして使用してください。1 つの変更は、すべての再試行を通じて同じイベント ID を保持し、新しい変更には、すでに通知を受けたレコードへの 2 回目の更新を含め、毎回新しい ID が付きます。

## 配信と再試行

配信は**少なくとも 1 回**行われます。エンドポイントが次のいずれかの場合、Alforse はその試行を失敗とみなし、指数バックオフで最大 8 回まで再試行します。

* 10 秒以内に成功ステータスを返さない。
* `5xx` または `429` を返す。

それ以外の結果は再試行されません。

* その他の `4xx` レスポンスは、その変更の配信を終了します。
* `3xx` レスポンスは、Alforse がリダイレクトに従わないため、失敗として扱われます。最終的な URL を設定してください。
* 拒否されたアドレスへの配信は再試行されません。

最後の試行が失敗すると、Alforse はその変更を失敗としてマークし、破棄します。

### エンドポイントを設計する

* **重複を想定してください。** 最初の試行をすでに処理した後で、再試行が届くことがあります。重複排除は、`type` と `record.id` の組み合わせではなく `x-omeriva-webhook-event` で行ってください。同じレコードに対する別々の変更が、これらの値を共有するためです。
* **障害後は照合してください。** 破棄された変更は再送されません。すべてのイベントを処理する必要がある場合は、エンドポイントの停止後に API を照会して追いつかせてください。

## 配信を検証する

Webhook を作成すると、その Webhook 固有の署名シークレットも生成され、Alforse が暗号化して保管します。`x-omeriva-webhook-signature` の署名は、`` `${timestamp}.${body}` `` の HMAC-SHA256 を 16 進数で表したもので、シークレット文字列をキーとします。`timestamp` は `x-omeriva-webhook-timestamp` の値、`body` は生のリクエスト本文です。

配信を検証する手順は次のとおりです。

1. パースする前に、生の本文を取得します。JSON をパースして再シリアライズするとバイト列が変わり、署名が一致しなくなります。
2. HMAC を計算し、`v1=` の後ろの値と、定数時間の比較で照合します。
3. 自分の時計から 5 分以上ずれているタイムスタンプは拒否します。これにより、傍受されたリクエストが再生されるのを防げます。再試行は毎回新しいタイムスタンプで再署名されるため、正当な再試行は通ります。

```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 を入力する。 |
| 同じ変更が 2 回処理される | 応答が遅い、または失敗した後に再試行が届いた | `x-omeriva-webhook-event` で重複排除する。 |
| 正当なリクエストで署名の検証に失敗する | ハッシュ化の前に本文がパースされた | 生の本文のバイト列をハッシュ化する。 |
| **テーブル**にテーブルが表示されない | そのテーブルは Webhook イベントを公開していない | 関連するテーブルを購読するか、API をポーリングする。 |

## 関連項目

**APIキー**

Webhook が変更を通知した後、Alforse API から完全なレコードを取得します。

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

**連携**

パッケージ化されたコネクターをお探しですか？ 自社提供の連携をご覧ください。

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


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