# API リファレンス

> Webhook Admin の公開 API（/v1）のリファレンスです。認証・エラー・レート制限と、各 API のパラメーターとレスポンスを説明します。

Source: https://webhookadmin.com/docs/api/

## 基本

- **URL**: `https://api.webhookadmin.com/v1`
- **認証**: `Authorization: Bearer sk_live_…` または `sk_test_…`。読み書きできるのは、そのキーの環境内のデータのみです
- **形式**: JSON。時刻は Unix ミリ秒、ID は接頭辞付きの文字列
- **ボディの上限**: 1.1MB（超えると 413 `too_large`）
- **一覧**: `?cursor=&limit=`（`limit` は 1〜100、既定 50）。レスポンスは `{ items, next_cursor }`

一覧の `cursor` と、メッセージ・配信の ID は、ログ保存期間の最も古い月から今月までのもののみ受け付けます（範囲外の `cursor` には 422、ID には 404 を返します）。

## エラー

```
{ "error": "<code>", "message": "<説明>" }
```

| HTTP | `error` | 説明 |
| --- | --- | --- |
| 401 | `unauthorized` | キーが無い、誤っている、失効済み、または期限切れ |
| 402 | `plan_limit` | プランの上限に達した |
| 403 | `forbidden` | キーのスコープに含まれない操作 |
| 404 | `not_found` | 対象が見つからない |
| 409 | `conflict` | 既存のデータと競合する |
| 409 | `endpoint_disabled` | エンドポイントが自動で無効化されている。設定を修正して再開してから再送信する |
| 413 | `too_large` | ボディが 1.1MB を超える |
| 422 | `invalid` | 入力内容の誤り |
| 429 | `rate_limited` | レート制限を超えた |

## レート制限

1 分あたりのリクエスト数を、環境ごとにすべてのキーの合計で数えます。

| プラン | 1 分あたり |
| --- | --- |
| Free | 600 |
| Starter | 3,000 |
| Pro | 12,000 |
| Business | 20,000 |

| レスポンスヘッダー | 説明 |
| --- | --- |
| `x-ratelimit-limit` | 1 分あたりの上限回数 |
| `x-ratelimit-remaining` | 現在の 1 分間の残り回数 |
| `x-ratelimit-reset` | 現在の 1 分間が終わる時刻（Unix 秒） |
| `retry-after` | 429 の場合のみ。再試行できるまでの秒数 |

## API 一覧

| メソッド | パス | スコープ |
| --- | --- | --- |
| POST | [`/v1/messages`](#send-message) | `messages:send` |
| GET | [`/v1/messages`](#list-messages) | `logs:read` |
| GET | [`/v1/messages/{id}`](#get-message) | `logs:read` |
| POST | [`/v1/deliveries/{id}/retry`](#retry-delivery) | `messages:retry` |
| GET | [`/v1/consumers`](#list-consumers) | `logs:read` |
| POST | [`/v1/consumers`](#create-consumer) | `consumers:write` |
| DELETE | [`/v1/consumers/{id}`](#delete-consumer) | `consumers:write` |
| POST | [`/v1/consumers/{id}/portal`](#create-portal-link) | `endpoints:write` |
| GET | [`/v1/endpoints`](#list-endpoints) | `logs:read` |
| POST | [`/v1/endpoints`](#create-endpoint) | `endpoints:write` |
| GET | [`/v1/endpoints/{id}`](#get-endpoint) | `logs:read` |
| PATCH | [`/v1/endpoints/{id}`](#update-endpoint) | `endpoints:write` |
| DELETE | [`/v1/endpoints/{id}`](#delete-endpoint) | `endpoints:write` |
| POST | [`/v1/endpoints/{id}/rotate-secret`](#rotate-secret) | `endpoints:write` |
| POST | [`/v1/endpoints/{id}/test`](#send-test) | `messages:send` |
| POST | [`/v1/destinations/test`](#test-destination) | `endpoints:write` |
| GET | [`/v1/endpoints/{id}/transformation`](#get-transformation) | `logs:read` |
| PUT | [`/v1/endpoints/{id}/transformation`](#set-transformation) | `endpoints:write` |
| DELETE | [`/v1/endpoints/{id}/transformation`](#delete-transformation) | `endpoints:write` |
| POST | [`/v1/endpoints/{id}/transformation/test`](#test-transformation) | `endpoints:write` |
| GET | [`/v1/poller/{endpoint_id}`](#poll) | ポーリングトークン（`sk_poll_…`。API キーではありません） |
| GET | [`/v1/endpoints/{id}/poller-tokens`](#list-poller-tokens) | `logs:read` |
| POST | [`/v1/endpoints/{id}/poller-tokens`](#create-poller-token) | `endpoints:write` |
| DELETE | [`/v1/endpoints/{id}/poller-tokens/{token_id}`](#delete-poller-token) | `endpoints:write` |
| POST | [`/v1/endpoints/{id}/recover`](#recover-endpoint) | `messages:retry` |
| GET | [`/v1/endpoints/{id}/recoveries/{recovery_id}`](#get-recovery) | `logs:read` |
| GET | [`/v1/event-types`](#list-event-types) | 不要（その環境の API キーであれば可） |
| POST | [`/v1/event-types`](#create-event-type) | `endpoints:write` |
| GET | [`/v1/sources`](#list-sources) | `logs:read` |
| POST | [`/v1/sources`](#create-source) | `endpoints:write` |
| GET | [`/v1/sources/{id}`](#get-source) | `logs:read` |
| PATCH | [`/v1/sources/{id}`](#update-source) | `endpoints:write` |
| DELETE | [`/v1/sources/{id}`](#delete-source) | `endpoints:write` |
| PUT | [`/v1/sources/{id}/secret`](#set-source-secret) | `endpoints:write` |
| GET | [`/v1/sources/{id}/requests`](#list-source-requests) | `logs:read` |
| POST | [`/v1/sources/{id}/requests/{request_id}/reprocess`](#reprocess-source-request) | `messages:send` |
| POST | [`/in/{token}`](#receive-webhook) | 受信 URL（API キーは不要） |

## メッセージの送信

```
POST /v1/messages
```

スコープ: `messages:send`

| パラメーター | 型 | 説明 |
| --- | --- | --- |
| `consumer` | string | 顧客 ID（`external_id`。200 文字まで） |
| `event_type` | string | イベントタイプ。英数字と `_ . : -` で 100 文字まで |
| `payload` | JSON | 配信するペイロード。1MB まで |
| `transformations_params` | object | 任意。トランスフォーメーションに `webhook.transformationsParams` として渡す値（JSON で 4KB まで）。`transformationsParams` の表記も受け付けます |
| `Idempotency-Key`（ヘッダー） | string | 任意。1〜256 文字 |

レスポンス: 202 `{ id, deliveries }`。`deliveries` は作成された配信の数です。

- 同じ `Idempotency-Key` には、24 時間は同じ `id` を返します（ペイロードが異なっても最初のメッセージの `id` です）。
- `consumer` に該当する顧客アカウントが無い場合は、自動で作成します（エンドポイントが無いため `deliveries` は 0 です）。
- `deliveries` が 0 のメッセージ（エンドポイントが無い）は、ステータス `no_endpoint` で記録のみ残します。配信は行わず、月間配信数にも数えません。後からエンドポイントを追加しても、このメッセージは配信されません。
- レスポンスヘッダー `x-usage-month` には、組織の今月（UTC）の本番環境の配信数（エンドポイントがあったメッセージの数）が入ります。
- テスト環境での送信とテスト送信は料金の対象外です。上限は 1 環境につき 1 日（UTC）1,000 通で、超えると 402 `plan_limit` を返します。

## メッセージの一覧

```
GET /v1/messages
```

スコープ: `logs:read`

クエリ: `status`（`pending` / `retrying` / `success` / `failed` / `cancelled` / `no_endpoint`）・`event_type`・`q`・`since`（Unix ミリ秒。この時刻以降に作成されたもの）・`cursor`・`limit`。

`no_endpoint` は、受け付けた時点で配信先のエンドポイントが 1 つも無かったメッセージです（顧客アカウントにエンドポイントが無い、または対象イベントが一致するエンドポイントが無い）。記録のみ残して配信は行わず、月間配信数にも数えません。後からエンドポイントを追加しても、このメッセージは配信されません。2026-10-05 より前は、これらを `pending` として返していました（それ以前に受け付けた分も、現在は `no_endpoint` として返します）。

`q` は、メッセージ ID・イベントタイプ・顧客 ID（`external_id`）・`Idempotency-Key` を部分一致で検索します。ペイロードの中身は検索しません。

`q`・`status`・`event_type` を指定して `since` を省略すると、直近 7 日間に作成されたメッセージのみを返します。

レスポンス: `{ items: MessageRow[], next_cursor }`。

## メッセージの詳細

```
GET /v1/messages/{id}
```

スコープ: `logs:read`

レスポンス: `MessageDetail`（ペイロードと、エンドポイントごとの配信・配信試行）。

## 配信の再送信

```
POST /v1/deliveries/{id}/retry
```

スコープ: `messages:retry`

`id` は `Delivery` の ID。レスポンス: 202。

- 配信中（`pending`・`retrying`）の場合は 409 `conflict` を返します。
- エンドポイントが自動で無効化されている（`disabled`）場合は、409 `endpoint_disabled` を返して受け付けません。エンドポイントの設定を修正し、[エンドポイントの更新](#update-endpoint) で `status: active` に戻してから呼び出してください。状態は `MessageDetail` の `deliveries[].endpoint_status` で事前に確認できます。
- 一時停止中（`paused`）のエンドポイントへの再送信は受け付けて保留し、再開時に配信します。エンドポイントが削除された配信は 404 `not_found` を返します。

## 顧客アカウントの一覧

```
GET /v1/consumers
```

スコープ: `logs:read`

クエリ: `cursor`・`limit`。レスポンス: `{ items: Consumer[], next_cursor }`。

## 顧客アカウントの作成

```
POST /v1/consumers
```

スコープ: `consumers:write`

| パラメーター | 型 | 説明 |
| --- | --- | --- |
| `external_id` | string | 自社サービスでの顧客 ID（200 文字まで） |
| `name` | string | 任意。200 文字まで |

レスポンス: 201 `Consumer`。同じ `external_id` の顧客アカウントが既にある場合は 409 `conflict` を返します。

## 顧客アカウントの削除

```
DELETE /v1/consumers/{id}
```

スコープ: `consumers:write`

顧客アカウントと、そのエンドポイント（署名シークレット・トランスフォーメーション・ポーリングエンドポイントのトークン）、この顧客アカウントに配信するソース（受信ログを含む）を削除します。この操作は取り消せません。レスポンス: 204。存在しない顧客アカウントや、別の環境の顧客アカウントには 404 を返します。

- 顧客ポータルのリンクは、有効期限内でも直ちに使用できなくなります。
- 未配信の配信（再送信待ちを含む）は配信しません。ログ上は失敗となり、ポーリングエンドポイントで待機中の配信はキャンセル（`cancelled`）になります。
- 配信ログは、プランのログ保存期間を過ぎた時点で、その日の他のログと一緒に削除されます。月間配信数の集計は残ります。
- 同じ `external_id` でメッセージを送信すると、エンドポイントの無い新しい顧客アカウントとして作成されます。

## 顧客ポータルのリンクの作成

```
POST /v1/consumers/{id}/portal
```

スコープ: `endpoints:write`

| パラメーター | 型 | 説明 |
| --- | --- | --- |
| `frame_origin` | string | 任意。iframe で埋め込む親ページのオリジン（`https://app.example.com`。パスは含めません） |
| `locale` | `ja`・`en` | 任意。表示言語。省略時はブラウザの言語 |

レスポンス: 201 `{ url, expires_at }`。`url` の有効期間は 15 分で、期限内であれば何度でも開けます。Free プランでは 402 `plan_limit`、別の環境の顧客アカウントには 404 を返します。

自社アプリへの埋め込み例（`src` にはレスポンスの `url` を指定）:

**HTML**

```
<iframe
  src="https://app.webhookadmin.com/portal/eyJlIjoi…?lang=ja"
  title="Webhook"
  allow="clipboard-write"
  style="display: block; width: 100%; height: 900px; border: 0"
></iframe>
```

- 埋め込めるのは `frame_origin` で指定したオリジンのみです。`frame_origin` を指定しないリンクは、iframe 内には表示されません（`Content-Security-Policy: frame-ancestors`）。
- ポータルは Cookie を使わず、リンクに含まれるトークンで認証します。別ドメインの iframe 内（Safari を含む）でも利用できます。
- 顧客が行える操作: エンドポイントの登録・編集・一時停止・削除、対象イベントの選択、署名シークレットの表示とローテーション、テスト送信、配信ログの確認と再送信。
- `url` にクエリ `theme=dark` を付けると、ダークテーマで表示します。

## エンドポイントの一覧

```
GET /v1/endpoints
```

スコープ: `logs:read`

クエリ: `consumer_id`（任意）。レスポンス: `{ items: Endpoint[] }`。

## エンドポイントの作成

```
POST /v1/endpoints
```

スコープ: `endpoints:write`

| パラメーター | 型 | 説明 |
| --- | --- | --- |
| `consumer_id` | string | 顧客アカウントの `id`（`con_…`） |
| `url` | string | エンドポイント URL（2,000 文字まで）。`type` が `http`（既定）の場合に指定 |
| `type` | string | 任意。エンドポイントの種類。`http`（既定）、`polling`（[ポーリングエンドポイント](#polling)。`url` と `destination` は指定しません）、または Webhook 以外のエンドポイント（[Webhook 以外のエンドポイント](#destinations)）。`webhook`・`pollingEndpoint` も受け付けます |
| `destination` | object | Webhook 以外のエンドポイントの設定と認証情報。`url` の代わりに指定します |
| `event_types` | string[] | 任意。対象イベントのイベントタイプ。省略時はすべて |
| `fixed_ip` | boolean | 任意。固定送信元 IP から配信する |
| `description` | string | 任意。200 文字まで |
| `retry` | object | 任意。再送信回数と再送信間隔（[再送信ポリシー](#retry-policy)）。省略時は既定値 |
| `compat_signature` | object | 任意。互換署名（[署名検証](https://webhookadmin.com/docs/sdk/#compat-signature)） |
| `ordering` | string | 任意。配信順序（[順序保証](#ordering)）。`none`（既定）または `fifo` |
| `body_format` | string | 任意。ペイロード形式。`standard`（既定。`{ type, timestamp, data }`）または `raw`（`data` そのもの） |
| `secret` | string | 任意。現在お使いの署名シークレット（`whsec_…`）。省略すると新しく生成します（[他サービスからの移行（Webhook）](#migrate-webhooks)） |

レスポンス: 201 `Endpoint` と `secret`（署名シークレット。作成時のみ）・`poller_url`（ポーリング用の URL。`http` の場合は `null`）。

- URL は https（ポート 443 または 8443）を指定します。
- IP アドレスを直接指定した URL、社内向けのホスト名、社内アドレスに解決されるホスト名、名前解決できないホスト名には 422 を返します。
- 1 つの顧客アカウントに登録できるエンドポイントは 20 個までです。超えると 422 を返します。

## エンドポイントの詳細

```
GET /v1/endpoints/{id}
```

スコープ: `logs:read`

レスポンス: `Endpoint` と `recent: AttemptRow[]`・`retrying_count`・`waiting_count`（順番待ちの件数。`ordering: fifo` の場合のみで、それ以外は `0`）・`recovery`（直近の一括再送信。無ければ `null`）・`poller_url`（ポーリングエンドポイントの URL。`http` の場合は `null`）。

## エンドポイントの更新

```
PATCH /v1/endpoints/{id}
```

スコープ: `endpoints:write`

パラメーター（すべて任意）: `url`・`event_types`・`status`（`active` / `paused`）・`description`・`retry`・`compat_signature`・`ordering`・`body_format`。レスポンス: `Endpoint`。

- `paused` にしたエンドポイントへの配信は保留されます。`active` に戻すと、今月と先月の保留分をまとめて配信します。
- 自動で無効化されたエンドポイント（`disabled`）は、`status` を `active` にすると再開します。
- `retry` に `null` を指定すると既定値に戻り、`compat_signature` に `null` を指定すると解除されます。
- `ordering` を `fifo` から `none` に戻すと、順番待ちの配信を直ちに配信します。
- `type` は作成後に変更できません（422）。
- 署名シークレット（`secret`）は更新では指定できません（422）。[署名シークレットのローテーション](#rotate-secret) で変更してください。

## エンドポイントの削除

```
DELETE /v1/endpoints/{id}
```

スコープ: `endpoints:write`

レスポンス: 204。

## 署名シークレットのローテーション

```
POST /v1/endpoints/{id}/rotate-secret
```

スコープ: `endpoints:write`

レスポンス: `{ secret }`。ローテーション後 24 時間は、旧シークレットによる署名も付けて配信します。

## エンドポイントへのテスト送信

```
POST /v1/endpoints/{id}/test
```

スコープ: `messages:send`

このエンドポイントだけにテストメッセージを 1 通送信します。リクエストボディは省略できます。

| パラメーター | 型 | 説明 |
| --- | --- | --- |
| `event_type` | string | 任意。既定値は `webhook.test` |
| `payload` | object | 任意。送信する `data`（JSON オブジェクト、1MB まで）。実際のイベントのサンプルを、このエンドポイントにだけ送信できます |

- `payload` を省略した場合、Webhook のエンドポイントには `{ "test": true, "endpoint_id": … }` を送信します。チャット（Slack・Teams・Chatwork・Google Chat）のエンドポイントには、題「テストメッセージ」・本文「Webhook Admin からのテスト送信です。この送信先に通知が届くことを確認できました。」のメッセージを送信します（組織の通知の言語が英語の場合は英語）。
- トランスフォーメーションは、通常のメッセージと同じく配信時に適用されます。
- テスト送信は月間上限の対象外で、テスト環境での送信と同じく 1 環境 1 日 1,000 通までです。
- エンドポイントが自動で無効化されている（`disabled`）場合は、409 `endpoint_disabled` を返します。

レスポンス: 202 `{ message_id }`。結果は [メッセージの詳細](#get-message) で確認できます。

## エンドポイントの接続テスト

```
POST /v1/destinations/test
```

スコープ: `endpoints:write`

| パラメーター | 型 | 説明 |
| --- | --- | --- |
| `endpoint_id` | string | 任意。保存済みのエンドポイントの設定と認証情報を使用 |
| `type` | string | `endpoint_id` を省略する場合は必須。Webhook 以外のエンドポイントの種類 |
| `destination` | object | `endpoint_id` を省略する場合は、認証情報を含めて必須。指定すると保存済みの設定を上書き |
| `fixed_ip` | boolean | 任意。固定送信元 IP から送信する。省略時はエンドポイントの設定に従う |

レスポンス: `{ ok, via, response_status, duration_ms, error, response_head, ref }`。その場で 1 通（`webhook.test`）を送信し、ログには残しません。チャット（Slack・Teams・Chatwork・Google Chat）には、[テスト送信](#send-test) と同じ題「テストメッセージ」の案内文を送信します。テスト環境での送信と同じく、1 日の上限に数えます。Pro プラン以上で利用できます。

## トランスフォーメーションの取得

```
GET /v1/endpoints/{id}/transformation
```

スコープ: `logs:read`

レスポンス: `{ endpoint_id, code, updated_at, enabled, variables }`。トランスフォーメーションが無い場合は 404 `not_found` を返します。

## トランスフォーメーションの保存

```
PUT /v1/endpoints/{id}/transformation
```

スコープ: `endpoints:write`

| パラメーター | 型 | 説明 |
| --- | --- | --- |
| `code` | string | `function handler(webhook)` を定義する JavaScript。51,200 文字まで（[トランスフォーメーション](#transformations)）。トランスフォーメーションが未設定の場合は必須 |
| `enabled` | boolean | 任意。`false` でコードを残したまま無効化、`true` で再び有効化 |
| `variables` | object | 任意。`webhook.env` に渡す値（文字列値で 50 個、JSON で 4KB まで）。`null` で削除 |

レスポンス: `{ endpoint_id, code, updated_at, enabled, variables }`。省略した項目は現在の値を保持します。Pro プラン以上で利用できます（`enabled: false` のみの保存は全プランで可能です）。構文エラーのコード、`handler` が無いコード、空のコードには 422（`fields.code`）を返します。

## トランスフォーメーションの解除

```
DELETE /v1/endpoints/{id}/transformation
```

スコープ: `endpoints:write`

レスポンス: 204。次回の配信試行から、トランスフォーメーションを適用せずに配信します。全プランで解除できます。

## トランスフォーメーションのテスト

```
POST /v1/endpoints/{id}/transformation/test
```

スコープ: `endpoints:write`

| パラメーター | 型 | 説明 |
| --- | --- | --- |
| `code` | string | 任意。テストするコード。省略時は保存済みのコード |
| `payload` | any | 任意。サンプルの `data`。省略時は `{ "test": true, "endpoint_id": … }` |
| `event_type` | string | 任意。既定値は `webhook.test` |
| `variables` | object | 任意。`webhook.env` に渡す値。省略時は保存済みの値 |
| `transformations_params` | object | 任意。`webhook.transformationsParams` に渡す値 |

レスポンス: `{ result: "send", method, url, headers, payload, changed, logs }`・`{ result: "cancel", logs }`・`{ result: "error", error: { kind, message }, logs }` のいずれか。配信も保存も行いません。`logs` には `console.log` の出力が入ります。

## メッセージの取得（ポーリング）

```
GET /v1/poller/{endpoint_id}
```

スコープ: ポーリングトークン（`sk_poll_…`。API キーではありません）

| クエリ | 型 | 説明 |
| --- | --- | --- |
| `iterator` | string | 任意。前回のレスポンスの `iterator`。省略時はログ保存期間の始め（エンドポイントの作成日）から |
| `limit` | number | 任意。1〜250（既定 50） |
| `event_type` | string | 任意。複数指定可。エンドポイントの `event_types` の中からさらに絞り込む |
| `after` | string | 任意。ISO 8601 形式の時刻。`iterator` が無い場合のみ、その時刻から取得 |
| `channel` | string | 任意。受け付けますが、チャンネル機能は未提供のため絞り込みは行いません |

レスポンス: `{ data: PolledMessage[], iterator, done }`。受け付けた順に返します。`done: true` は、現時点で続きが無いことを示します。受信側が `Authorization: Bearer sk_poll_…` を付けて呼び出します（[ポーリングエンドポイント](#polling)）。

互換パスの API は [他サービスからの移行（ポーリングエンドポイント）](#migrate-polling) を参照してください。

## ポーリングトークンの一覧

```
GET /v1/endpoints/{id}/poller-tokens
```

スコープ: `logs:read`

レスポンス: `{ items: PollerToken[] }`（作成順）。トークンの文字列は返しません。

## ポーリングトークンの作成

```
POST /v1/endpoints/{id}/poller-tokens
```

スコープ: `endpoints:write`

パラメーター: `name`（任意、100 文字まで）。リクエストボディは省略できます。

レスポンス: 201 `PollerToken` と `token`（`sk_poll_…`。作成時のみ）。Starter プラン以上で利用できます。1 つのエンドポイントにつき 5 個まで作成でき、超えると 422 を返します。`http` のエンドポイントには 404 を返します。

## ポーリングトークンの削除

```
DELETE /v1/endpoints/{id}/poller-tokens/{token_id}
```

スコープ: `endpoints:write`

レスポンス: 204。削除したトークンでは、直ちにポーリングできなくなります。

## 一括再送信

```
POST /v1/endpoints/{id}/recover
```

スコープ: `messages:retry`

| パラメーター | 型 | 説明 |
| --- | --- | --- |
| `since` | number | Unix ミリ秒。この時刻以降に作成されたメッセージが対象。ログ保存期間内の過去の時刻を指定 |

レスポンス: 202 `Recovery`。進捗は [一括再送信の状態](#get-recovery) で確認します。

- このエンドポイントへの配信が失敗で終わったものを、同じメッセージ（同じ `webhook-id`）のまま再送信します（[一括再送信](#recovery)）。
- エンドポイントが自動で無効化されている（`disabled`）場合は 409 `endpoint_disabled`、一時停止中（`paused`）の場合は 409 `conflict` を返します。再開してから呼び出してください。
- 1 つのエンドポイントで同時に実行できる一括再送信は 1 つだけです。実行中は 409 `conflict` を返します。

## 一括再送信の状態

```
GET /v1/endpoints/{id}/recoveries/{recovery_id}
```

スコープ: `logs:read`

レスポンス: `Recovery`。`status` は `running`（実行中）・`done`（完了）・`stopped`（完了前にエンドポイントが一時停止・無効化・削除された）のいずれかです。

## イベントタイプの一覧

```
GET /v1/event-types
```

スコープ: 不要（その環境の API キーであれば可）

レスポンス: `{ items: EventType[] }`（名前順。ページングはありません）。

## イベントタイプの登録

```
POST /v1/event-types
```

スコープ: `endpoints:write`

| パラメーター | 型 | 説明 |
| --- | --- | --- |
| `name` | string | イベントタイプ。英数字と `_ . : -` で 100 文字まで |
| `description` | string | 任意。200 文字まで |

レスポンス: 201 `EventType`。同じ `name` のイベントタイプが既にある場合は 409 `conflict` を返します。

登録したイベントタイプは、管理画面と顧客ポータルでエンドポイントの対象イベントを選ぶときの候補に表示されます。

## ソースの一覧

```
GET /v1/sources
```

スコープ: `logs:read`

レスポンス: `{ items: Source[], next_cursor }`（作成順）。

## ソースの作成

```
POST /v1/sources
```

スコープ: `endpoints:write`

| パラメーター | 型 | 説明 |
| --- | --- | --- |
| `name` | string | 1〜100 文字 |
| `provider` | string | `stripe`・`github`・`shopify`・`slack`・`meta`・`zoom`・`standard`・`hmac`・`basic`・`api_key`・`none` |
| `consumer_id` | string | 配信先の顧客アカウントの ID |
| `secret` | string | 任意。署名シークレット（500 文字まで）。`standard` の場合は `whsec_…`。後から [署名シークレットの登録](#set-source-secret) でも登録できます |
| `config` | object | 任意。`SourceConfig`（[ソースの設定](#source-config)） |
| `status` | string | 任意。`active`（既定）または `paused` |

レスポンス: 201 `Source`。`url` が受信 URL です。ソースの数がプランの上限を超える場合は 402 `plan_limit`、顧客アカウントが見つからない場合は 404 を返します。

## ソースの詳細

```
GET /v1/sources/{id}
```

スコープ: `logs:read`

レスポンス: `Source`。署名シークレットは、マスクしたヒント（`secret_hint`）のみを返します。

## ソースの更新

```
PATCH /v1/sources/{id}
```

スコープ: `endpoints:write`

パラメーター: `name`・`provider`・`consumer_id`・`status`・`config`（すべて任意）。レスポンス: `Source`。

- `provider` を変更すると、署名シークレットは解除されます。新しいサービスの署名シークレットを [署名シークレットの登録](#set-source-secret) で登録してください。
- `provider` を変更して `config` を省略すると、その `provider` の既定の設定が適用されます。`config` を指定すると、設定を置き換えます。
- 署名シークレットは [署名シークレットの登録](#set-source-secret) で再登録します（`secret` を指定すると 422）。
- `status: paused` にすると受信したリクエストを保留します。`active` に戻すと再開し、保留したリクエストを配信します（[一時停止](#source-paused)）。

## ソースの削除

```
DELETE /v1/sources/{id}
```

スコープ: `endpoints:write`

レスポンス: 204。以降、受信 URL は 404 を返します。受信ログも削除されます。

## 署名シークレットの登録

```
PUT /v1/sources/{id}/secret
```

スコープ: `endpoints:write`

| パラメーター | 型 | 説明 |
| --- | --- | --- |
| `secret` | string \| null | 各サービスの管理画面に表示される署名シークレット（500 文字まで）。`standard` は `whsec_…`、それ以外は空白を含まない文字列。`null` で解除 |

レスポンス: `Source`。再登録後も 24 時間は、以前の署名シークレットで署名されたリクエストを受け付けます。`null` を指定すると、以前のものを含めてすべて解除します。

## ソースの受信ログ

```
GET /v1/sources/{id}/requests
```

スコープ: `logs:read`

クエリ（すべて任意）: `cursor`・`limit`・`status`（`accepted`・`duplicate`・`rejected`・`held` のいずれかで絞り込み）。レスポンス: `{ items: SourceRequest[], next_cursor }`（新しい順。プランのログ保存期間内のみ）。

| `status` | 説明 |
| --- | --- |
| `accepted` | 受付済み。`message_id` は作成したメッセージの ID |
| `duplicate` | 重複。同じ配信 ID の再送信で、`message_id` は前回と同じメッセージの ID |
| `rejected` | 拒否。`reason` に英語の理由 |
| `held` | 保留。一時停止中に受信したリクエストで、再開時に処理します |

- `method` は受信したメソッド（`POST`・`PUT`・`PATCH`・`DELETE`・`GET`）です。
- `processed_at` は、保留または拒否したリクエストを後から処理した時刻です（未処理の場合は `null`）。
- `reprocessable: true` のリクエストは [拒否したリクエストの再処理](#reprocess-source-request) を実行できます。
- `headers` には受信したヘッダーが入ります（認証情報・Cookie などは除きます）。

## 拒否したリクエストの再処理

```
POST /v1/sources/{id}/requests/{request_id}/reprocess
```

スコープ: `messages:send`

拒否したリクエストを、現在の署名シークレットと設定で再検証してから処理します（[再処理](#source-reprocess)）。レスポンス: 更新後の `SourceRequest`。

- 受け付けると `accepted`（または `duplicate`）になり、月間配信数に数えます。一時停止中のソースでは `held` になります。
- 検証に失敗した場合は `rejected` のままで、`reason` と `processed_at` を更新します。
- 拒否以外のリクエストと、ボディが保存されていないリクエスト（`reprocessable: false`）には 409、ログ保存期間を過ぎたリクエストには 404 を返します。

## 受信 URL

```
POST /in/{token}
```

スコープ: 受信 URL（API キーは不要）

送信元サービスが Webhook を送信する URL です（[ソース](#sources)）。POST・PUT・PATCH・DELETE を受け付けます（GET は Meta のサブスクリプション確認のみ）。ボディは 10MB まで。署名を検証してから、ソースの顧客アカウント宛てのメッセージを作成します。

| HTTP | 説明 |
| --- | --- |
| 200 | 受付済み。`{ "id": "msg_…" }`（[受付時のレスポンス](#source-response) を設定した場合はそのレスポンス）。同じ配信 ID の再送信には同じ ID を返します。一時停止中は保留して `{ "status": "held", "request_id": "sreq_…" }` を返します。Slack・Zoom・Meta の URL 確認には、各サービスの形式で応答します |
| 401 | 署名の不一致、署名ヘッダーが無い、時刻のずれが 5 分以上、署名シークレットが未登録、Basic 認証や API キーの不一致（Basic 認証は `WWW-Authenticate: Basic` 付き） |
| 402 | プランのソース数の上限を超えている |
| 403 | 組織が停止されている、または Meta のサブスクリプション確認で `hub.verify_token` が一致しない |
| 404 | 受信 URL が見つからない |
| 405 | POST・PUT・PATCH・DELETE 以外のメソッド（`meta` の GET を除く） |
| 413 | ボディが 10MB を超える |
| 429 | 1 つのソースで 10 秒間に 1,000 回を超えた、`none` のソースで API の 1 分あたりのレート制限を超えた（いずれも `retry-after` 付き）、月間上限に達した、テスト環境の 1 日の上限に達した |

拒否した場合のレスポンスボディは `{ "error": "<英語の理由>" }` です（例: `invalid signature`・`invalid credentials`・`invalid API key`・`timestamp outside the tolerance`・`signing secret is not set`）。

## 再送信ポリシー

再送信回数と再送信間隔は、エンドポイントごとに設定できます。設定は管理画面・API（`retry`）・MCP・顧客ポータルで行います。

| 項目 | 設定値 |
| --- | --- |
| `count` | `0`・`1`・`2`・`3`・`5`・`7`・`12`（初回配信後の再送信回数。`0` は再送信しない） |
| `interval` | `progressive`（5 秒・5 分・30 分・2 時間・5 時間・10 時間・10 時間、以降は 10 時間ごと）、`1m`・`5m`・`30m`・`1h`・`6h`（固定間隔） |

```
{ "retry": { "count": 3, "interval": "5m" } }
```

- 既定値は `count: 7`・`interval: progressive` です（初回配信を含めて最大 8 回、約 28 時間）。`retry: null` で既定値に戻ります。
- 初回配信から最後の再送信までが 3 日を超える組み合わせは設定できません（422）。該当するのは `progressive` と `12` の組み合わせです。
- 失敗が 5 日間続いたエンドポイントの自動無効化は、再送信ポリシーとは別に判定します。
- 失敗が続いているエンドポイントへの配信は、一定時間停止します。その間の配信は行わずに再送信回数に数え、再送信ポリシーに従って次の時刻に再送信します。再送信回数を使い切ると失敗となります。

## 順序保証

エンドポイントごとに、受け付けた順に 1 通ずつ配信する順序保証（FIFO）を設定できます（`ordering: fifo`、既定は `none`）。設定は管理画面・API・MCP・顧客ポータルで行います。

```
{ "ordering": "fifo" }
```

- 同時に配信するのは 1 通のみです。前の 1 通が配信成功または失敗で完了するまで、次の 1 通は配信しません。
- 配信順序は API が 202 を返した順です。前のメッセージの 202 を受け取ってから送信したメッセージは、必ず後に配信されます。
- 前の 1 通が失敗し続けている間は、後続の配信も待機します。前の 1 通は再送信ポリシーに従って再送信し、回数を使い切ると失敗として次へ進みます。
- 後続が待機している間は、アラートに待機件数を記載します。エンドポイントの詳細（`waiting_count`）でも確認できます。
- 失敗した配信を再送信すると（個別・一括とも）、現在待機中の配信の後に配信します。
- 一時停止中に受け付けた配信も順序を保持し、再開すると先頭から順に配信します。
- `none` に戻すと、待機中の配信を直ちに並行して配信します。`fifo` に設定する前に受け付けた配信は、順序保証の対象外です。
- 順序保証はエンドポイント単位です。同じメッセージの他のエンドポイントには影響しません。
- 1 通ずつ配信するため、配信速度は受信側の応答時間に依存します。

## Webhook 以外のエンドポイント

エンドポイントの `type` を `http` 以外にすると、Webhook の代わりにキュー・イベントバス・オブジェクトストレージ・チャットへ配信します。ペイロードは Webhook のエンドポイントと同じです（既定は Standard Webhooks の JSON。`body_format: raw` の場合は `data` そのもの。チャットには [メッセージとして投稿します](#destinations-chat)）。Pro プラン以上で、管理画面・API・MCP・顧客ポータルから設定できます。

```
{
  "consumer_id": "con_…",
  "type": "sqs",
  "destination": {
    "region": "ap-northeast-1",
    "account_id": "123456789012",
    "queue_name": "orders",
    "credentials": { "access_key_id": "AKIA…", "secret_access_key": "…" }
  }
}
```

| `type` | `destination` | `credentials` | 署名と ID |
| --- | --- | --- | --- |
| `sqs` | `region`・`account_id`・`queue_name` | `access_key_id`・`secret_access_key` | メッセージの属性 |
| `eventbridge` | `region`・`event_bus`（既定 `default`）・`source`（既定 `webhookadmin`）・`detail_type`（既定はイベントタイプ） | SQS と同じ | なし |
| `pubsub` | `project_id`・`topic` | `service_account`（サービスアカウントキーの JSON） | メッセージの属性 |
| `s3` | `region`・`bucket`・`prefix` | SQS と同じ | オブジェクトのメタデータ |
| `r2` | `account_id`・`bucket`・`prefix` | R2 の API トークンのアクセスキー | オブジェクトのメタデータ |
| `gcs` | `bucket`・`prefix` | HMAC キーまたは `service_account` | オブジェクトのメタデータ |
| `azure_blob` | `account`・`container`・`prefix` | `account_key` | BLOB のメタデータ（`webhook_id` など） |
| `servicebus` | `namespace`・`entity`（キューまたはトピック） | `key_name`・`key`（送信権限を持つ SAS ポリシー） | メッセージのプロパティ |
| `kafka` | `url`（Confluent Cloud の REST エンドポイント。`/kafka/v3/…` を付けて送信するため、単体の REST Proxy には配信できません）・`cluster_id`・`topic` | `api_key`・`api_secret` | レコードのヘッダー |
| `rabbitmq` | `url`（HTTP API の起点）・`vhost`（既定 `/`）・`exchange`・`routing_key` | `username`・`password` | メッセージのヘッダー |
| `slack` | なし | `webhook_url`（Slack の Incoming Webhook の URL） | 署名なし |
| `teams` | なし | `webhook_url`（Teams のワークフローの URL） | 署名なし |
| `chatwork` | `room_id`（チャット ID の数字） | `api_token` | 署名なし |
| `google_chat` | なし | `webhook_url`（Google Chat のスペースの Webhook の URL） | 署名なし |

- 署名と ID は、`webhook-id`・`webhook-timestamp`・`webhook-signature`・`event-type` として、配信先のサービスが保持できる場所に格納します。受信側は Webhook と同じ SDK でペイロードを検証できます（チャットには付与しません）。
- 配信先のホストは、`region` などの値から各事業者の公式エンドポイントを組み立てます。Kafka と RabbitMQ の `url` は、エンドポイント URL と同じ制約（https・ポート 443 または 8443・社内アドレスは不可）です。
- オブジェクトストレージには、1 通を 1 つのオブジェクト `<prefix><年>/<月>/<日>/<メッセージの ID>.json` として保存します。再送信時は同じ名前で上書きします。
- 認証情報は暗号化して保存し、API では返しません。返すのはマスクしたヒント（`destination.credentials_hint`）のみです。更新時に `credentials` を省略すると、現在の認証情報を使用します。Kafka と RabbitMQ で `url` のホストを変更する場合は、認証情報を再入力してください。
- 再送信・再送信ポリシー・順序保証・トランスフォーメーション（ペイロードの変更とキャンセルのみ）・アラート・固定送信元 IP は、Webhook と同様に機能します。2xx が返っても配信先が受け付けなかったもの（EventBridge の失敗エントリなど）は失敗として扱います。
- 配信試行の `via` はエンドポイントの種類（`sqs` など）、`ref` は配信先が返した ID（SQS の MessageId・オブジェクト名・Kafka のパーティションとオフセットなど）です。
- [エンドポイントの接続テスト](#test-destination) で、保存前に 1 通送信して接続を確認できます。
- `type` には、互換の種類名（`eventBridge`・`googleCloudPubSub`・`amazonS3`・`googleCloudStorage`・`azureBlobStorage`・`rabbitMq`）も指定できます。

### チャット（Slack・Teams・Chatwork・Google Chat）

チャットには、JSON ではなくメッセージとして投稿します。ペイロードの `data`（`body_format: raw` の場合や、トランスフォーメーションで `data` が無くなった場合はペイロード全体）に文字列の `text` があれば、`title`（無ければイベントタイプ）・`text`・`lines`（文字列の配列、50 行まで）・`link`（https の URL のみ）からメッセージを組み立てます。`text` が無い場合は、件名をイベントタイプ、本文を `data` の JSON とし、上限を超える部分は切り捨てます（Slack は約 3,000 字、Teams は 28 KB、Chatwork は 60,000 字、Google Chat は全体で 4,000 字）。メッセージ ID を小さく添えます。

```
{
  "consumer_id": "con_…",
  "type": "chatwork",
  "destination": {
    "room_id": "123456789",
    "credentials": { "api_token": "…" }
  }
}

// ペイロードの data（またはトランスフォーメーション後のペイロード）
{
  "title": "請求書が支払われました",
  "text": "INV-1024 が支払われました（12,000 円）",
  "lines": ["顧客: 株式会社サンプル", "支払い方法: カード"],
  "link": "https://example.com/invoices/INV-1024"
}
```

- Slack: Incoming Webhook の URL（`https://hooks.slack.com/services/T…/B…/…`）を `webhook_url` に指定します。
- Teams: チャネルの［ワークフロー］で「Webhook 要求を受信したらチャネルに投稿する」テンプレートから作成した URL（`*.powerplatform.com`・`*.logic.azure.com`）を `webhook_url` に指定します。フローの実行権限は「すべてのユーザー」に設定してください。Adaptive Card 形式で投稿します。旧 Office 365 コネクタの URL（`webhook.office.com`）は使用できません。
- Chatwork: 右上の利用者名 →［サービス連携］→［API トークン］で発行したトークンを `api_token` に、投稿先のチャット ID を `room_id` に指定します。個人向け以外のプランでは、組織管理者の承認が必要です。
- Google Chat: スペース名の横のメニュー →［アプリと統合］→［Webhook を追加］で作成した URL（`https://chat.googleapis.com/v1/spaces/…/messages?key=…&token=…`）を `webhook_url` に指定します。件名は太字、リンクは `<URL|ホスト名>` の形式で投稿します。Google Workspace の管理者が Webhook を制限している場合は利用できません。
- トランスフォーメーションで `webhook.payload.data = { text: … }`（`body_format: raw` の場合は `webhook.payload = { text: … }`）のように指定すると、投稿するメッセージを指定できます。
- 署名は付与しません。URL と API トークンがシークレットの役割を果たします。
- URL やトークンが無効になった場合（Slack の 403・404・410、Teams の 401・403・404・410、Chatwork の 401・403・404、Google Chat の 401・403・404）は再送信せず、直ちにエンドポイントを `disabled` にして、理由（`disabled_reason`）を添えて無効化のアラートを送ります。認証情報を修正してから `status` を `active` に戻してください。
- 400・413 の場合は、その 1 通のみ再送信せずに失敗とします（エンドポイントは無効化しません）。429 の場合は `Retry-After`（Chatwork は `x-ratelimit-reset`）の時刻以降に、最長 1 時間後までに再送信します（固定送信元 IP から配信する場合は通常の再送信間隔です）。5xx・タイムアウトは通常どおり再送信します。
- 配信試行の `ref` は Chatwork のメッセージ ID です（Slack・Teams・Google Chat は `null`）。

## 他サービスからの移行（Webhook 以外のエンドポイント）

`POST /api/v1/app/{app_id}/destination` は、互換形式のエンドポイント設定（`type`・`config`・`eventTypes`・`description`）を受け付けて、エンドポイントを作成します。`app_id` には顧客アカウントの `id` または `external_id` を指定し、API キー（`endpoints:write`）で認証します。エラーは `{ code, detail }` の形式で、`detail` は英語です。Webhook のエンドポイントの移行は [他サービスからの移行（Webhook）](#migrate-webhooks) を参照してください。

| 互換の `type` | Webhook Admin の `type` | `config` の扱い |
| --- | --- | --- |
| `sqs` | `sqs` | `queueUrl` から region・アカウント・キュー、`accessKeyId`・`secretAccessKey` |
| `eventBridge` | `eventbridge` | `region`・`eventBusName`・`detailType`・アクセスキー |
| `googleCloudPubSub` | `pubsub` | `projectId`・`topicId`・`credentials`（サービスアカウントキーの JSON） |
| `amazonS3` | `s3` | `bucket`・`region`・アクセスキー |
| `googleCloudStorage` | `gcs` | `bucket`・`credentials`（サービスアカウントキーの JSON） |
| `azureBlobStorage` | `azure_blob` | `account`・`container`・`accessKey` |
| `rabbitMq` | `rabbitmq` | `uri` は HTTP API の URL（`https://ユーザー名:パスワード@ホスト/`）、`routingKey` |

- メッセージは 1 件ずつ配信します。`batchSize`・`maxWaitSecs` は指定できますが、配信には反映しません。
- ペイロードは既定で Standard Webhooks の JSON（`body_format: raw` の場合は `data` の値）です。署名と ID は属性やメタデータに付与し、Webhook と同じ SDK で検証できます。
- RabbitMQ の `amqp://`・`amqps://` には対応していません。管理プラグインの HTTP API の URL を指定してください。
- `roleArn`・`externalId`（IAM ロールの引き受け）と `endpointUrl` には対応していません。アクセスキーを指定してください。
- SNS・BigQuery・ClickHouse・Snowflake・Redshift・Postgres・OpenTelemetry への配信には対応していません。
- `uid` は使用しません。作成したエンドポイントの ID は `ep_…` で、取得・更新はエンドポイントの API で行います。

## トランスフォーメーション

エンドポイントごとに JavaScript の `function handler(webhook)` を保存すると、配信の直前にペイロード・ヘッダー・URL のパスとクエリ・HTTP メソッドを変更したり、配信をキャンセルしたりできます。Pro プラン以上で、管理画面・API・MCP から設定できます。

```
function handler(webhook) {
  const d = webhook.payload.data;
  webhook.payload = { text: `Invoice ${d.id} paid (${d.amount})` };
  webhook.headers['x-tenant'] = 'acme';
  webhook.url = webhook.url + '?source=webhookadmin';
  if (d.amount === 0) webhook.cancel = true;
  return webhook;
}
```

| 項目 | 説明 |
| --- | --- |
| `payload` | 配信するペイロード。`{ type, timestamp, data }`（`body_format: raw` のエンドポイントでは `data` そのもの）。任意の JSON 値に置き換えられます（1MB まで） |
| `headers` | 追加するヘッダー（20 個・合計 8KB まで）。`webhook-*`・`content-type` など配信に使うヘッダーは設定できません |
| `method` | `POST`・`PUT`・`PATCH` |
| `url` | スキーム・ホスト・ポートはそのままで、パスとクエリのみ変更できます |
| `cancel` | `true` で配信しません。配信は `cancelled` となります（失敗ではなく、再送信しません） |
| `eventType` | イベントタイプ（読み取り専用） |
| `env` | トランスフォーメーションと一緒に保存した `variables`（読み取り専用。無ければ `{}`） |
| `transformationsParams` | メッセージの送信時に指定した `transformations_params`（読み取り専用。指定しなかった場合は項目自体がありません） |

- 配信試行ごとに、保存されている元のペイロードから実行します。署名はトランスフォーメーション後のペイロードで生成します。
- 例外、上限超過、戻り値の誤りが発生した場合は配信せずに失敗とし、再送信しません。配信試行の `error` に `transformation: <種類>: <説明>` が記録されます。コードを修正してから再送信してください。
- 1 回の実行あたりの処理量とメモリに上限があります。コードは 51,200 文字までです。外部との通信はできません。
- トランスフォーメーションに渡せるペイロードは 1MB までです。ソースで受信した 1MB を超えるボディにはトランスフォーメーションを適用できず、配信は失敗となります（[受信ボディの転送](#source-raw)）。
- `variables` は文字列値で 50 個・JSON で 4KB まで、`transformations_params` は JSON で 4KB までです。
- `enabled: false` で保存すると、コードと `variables` を保持したまま、トランスフォーメーションを適用せずに配信します。無効化は全プランで可能です。
- トランスフォーメーションでペイロードなどを変更した配信試行は `transform: "changed"` になります。
- [トランスフォーメーションのテスト](#test-transformation) で、配信せずに結果と `console.log` の出力を確認できます。
- 顧客ポータルには表示されません。

## ポーリングエンドポイント

`type: polling` のエンドポイントは URL を持ちません。受信側がポーリングトークン（`sk_poll_…`）を付けて [メッセージの取得](#poll) を呼び出し、受け付けた順にメッセージを取得します。外部から到達できない環境や、Webhook の受信口を公開できない環境でも受信できます。Starter プラン以上で、管理画面・API・MCP・顧客ポータルから作成できます。

```bash
curl -H "Authorization: Bearer sk_poll_..." \
  "https://api.webhookadmin.com/v1/poller/ep_...?iterator=<前の iterator>&limit=100"
```

```
{
  "data": [
    {
      "id": "msg_2026092912ab34cd56ef7800",
      "event_type": "invoice.paid",
      "timestamp": "2026-09-29T03:04:05.000Z",
      "payload": { "type": "invoice.paid", "timestamp": "2026-09-29T03:04:05.000Z", "data": { "id": "inv_1" } },
      "headers": { "webhook-id": "msg_…", "webhook-timestamp": "1790651045", "webhook-signature": "v1,…" }
    }
  ],
  "iterator": "MjAyNi0wOS0yOS4wLjE",
  "done": true
}
```

- `payload`・`id`・`headers`（`webhook-id`・`webhook-timestamp`・`webhook-signature`）は、Webhook で配信する場合と同じです。通信はトークンと TLS で保護されているため署名の検証は不要ですが、エンドポイントの署名シークレットで `JSON.stringify(payload)` を検証することもできます。
- 次の呼び出しに前回のレスポンスの `iterator` を付けると、前のページを受信済みとみなし、その配信を `success` にします（取得時刻は配信試行 `via: "poll"` に記録）。処理が完了してから `iterator` を保存し、次回はそこから取得してください。
- 少なくとも 1 回の配信（at-least-once）を保証します。前回の `iterator` で呼び出すと、同じメッセージを再度返します。`iterator` を付けない場合はログ保存期間の始めから取得するため、必要に応じて `id` で重複を除外してください。
- トークンで取得できるのは、発行元のエンドポイントのメッセージのみです。他のエンドポイントには 403 を返します。トークンは 1 つのエンドポイントにつき 5 個まで作成でき、削除すると直ちに無効になります。呼び出しは 1 つのエンドポイントにつき 1 分あたり 600 回までです。
- ログ保存期間内に取得されなかったメッセージは、ログと一緒に削除されます。組織で設定した時間（既定 24 時間）ポーリングされない場合は、[アラート](#alert-conditions) で通知します。
- 再送信・一括再送信・トランスフォーメーション・固定 IP・互換署名・`ordering: fifo` には対応していません（受け付けた順に取得するため、順序は保証されます）。
- 一時停止中、およびプランのダウングレード後に受け付けたメッセージは、このエンドポイントでは取得できません。エンドポイントを削除すると、未取得のメッセージは `cancelled` になります。
- `event_type` で絞り込んだ場合、対象外のメッセージも次の呼び出しで受信済みとして扱います。
- SDK（Node.js・Python・Go・PHP）の `Poller` は、ページを順に取得して `iterator` を返します。

## 他サービスからの移行（ポーリングエンドポイント）

互換形式のポーリングで取得している受信側は、接続先の URL を `https://api.webhookadmin.com` に、トークンを `sk_poll_…` に変更するだけで、同じ形式で取得できます。

| メソッド | パス | 説明 |
| --- | --- | --- |
| GET | `/api/v1/app/{app_id}/poller/{endpoint_id}` | `{ data: [{ id, eventType, payload, timestamp }], iterator, done }`。`payload` は送信した `data` の値（エンドポイントの `body_format` に関係なく同じ）。`app_id` には顧客アカウントの `id` または `external_id` を指定 |
| GET | `/api/v1/app/{app_id}/poller/{endpoint_id}/consumer/{consumer_id}` | 読み取り位置を Webhook Admin 側で保持し、取得のたびに進めます |
| POST | `…/consumer/{consumer_id}/seek` | `{ after }`（ISO 8601）→ `{ iterator }`。読み取り位置を指定の時刻に合わせます |
| GET | `/api/v1/auth/app/{app_id}/poller/{endpoint_id}/token` | 送信側の API キー（`endpoints:write`）で、現在のトークン `{ token, id, name, createdAt, expiresAt, scopes }` を取得。無い場合は作成します |
| POST | `…/token/rotate` | `{ expiry?, oldTokenExpiry? }`（秒）。以前のトークンは既定で 300 秒後に無効になり、`0` を指定すると即時に無効になります |

- エラーは `{ code, detail }` の形式で、`detail` は英語です。形式が正しくない `iterator` には 400 `invalid_iterator` を返します。
- `event_type`・`channel`・`after` のクエリにも対応しています（[メッセージの取得](#poll)）。
- エンドポイントの種類には `http`・`polling` のほか、`pollingEndpoint` も指定できます。
- v2 の API（offset と commit）には対応していません。
- Webhook のエンドポイントの移行は [他サービスからの移行（Webhook）](#migrate-webhooks) を参照してください。

## 他サービスからの移行（Webhook）

署名シークレット（`whsec_…`）とペイロード形式をエンドポイントごとに指定できます。そのため、Standard Webhooks 形式で送信しているサービスから、受信側の設定と署名検証の実装を変更せずに移行できます。`function handler(webhook)` 形式のトランスフォーメーションのコードも、下記の相違点に該当しない限り修正は不要です。Webhook 以外のエンドポイントは [他サービスからの移行（Webhook 以外のエンドポイント）](#migrate-destinations)、ポーリングエンドポイントは [他サービスからの移行（ポーリングエンドポイント）](#migrate-polling) を参照してください。

| 移行元の項目 | Webhook Admin での対応 |
| --- | --- |
| 署名シークレット（`whsec_…`） | エンドポイントの作成時に `secret` で指定 |
| ペイロード（`data` のみを送る形式） | エンドポイントに `body_format: "raw"` を指定 |
| メッセージの送信（`POST /api/v1/app/{app_id}/msg`） | [`POST /v1/messages`](#send-message) で送信。`consumer` に顧客 ID（`external_id`）を指定 |
| トランスフォーメーション（`handler(webhook)`） | 同じ関数の形式に対応。`webhook.env`・`webhook.transformationsParams` も利用可能 |

**POST /v1/endpoints**

```
{
  "consumer_id": "con_…",
  "url": "https://example.com/webhooks",
  "secret": "whsec_…",
  "body_format": "raw"
}
```

- `secret` には、`whsec_` に続けて標準の base64 で表したシークレット（デコード後 24〜75 バイト）を指定します。`whsec_` は省略できます。非対称鍵（`whsk_`・`whpk_`）には対応していません（422）。
- 指定した署名シークレットは、自動生成したものと同様に暗号化して保存します。表示と [ローテーション](#rotate-secret)（ローテーション後 24 時間は旧シークレットの署名も併せて配信）も同じ扱いです。
- `secret` を指定できるのは、API・MCP・SDK から作成する場合です。管理画面と顧客ポータルから作成した場合は、新しい署名シークレットを生成します。
- `body_format` は管理画面・API・MCP・SDK・顧客ポータルで設定できます。`raw` の場合も署名は `webhook-id`・`webhook-timestamp`・`webhook-signature` ヘッダーに付与し、計算方法は Standard Webhooks の仕様どおりです。

トランスフォーメーションは、互換パスの API でも取得・更新できます（Pro プラン以上）。

| メソッド | パス | 説明 |
| --- | --- | --- |
| GET | `/api/v1/app/{app_id}/endpoint/{endpoint_id}/transformation` | `{ code, enabled, variables, updatedAt }`。トランスフォーメーションが無い場合、`code`・`variables`・`updatedAt` は `null`、`enabled` は `false`（`logs:read`） |
| PATCH | 同じパス | `{ code?, enabled?, variables? }` → 204。`code` は 10〜51,200 文字、`null` で解除。省略した項目は現在の値を保持（`endpoints:write`） |
| POST | `…/transformation/simulate` | `{ code, eventType, payload, variables? }` → `{ url, payload, method, cancel }`。`payload` は配信するペイロードの文字列。配信も保存も行わない（`endpoints:write`） |

- `app_id` には顧客アカウントの `id` または `external_id` を指定し、API キーで認証します。
- `enabled: false` にすると、コードと `variables` を保持したまま、トランスフォーメーションを適用せずに配信します。
- `body_format: "raw"` のエンドポイントでは、`webhook.payload` は `data` の値です。
- これらの API のエラーは `{ code, detail }` の形式で、`detail` は英語です。422 の場合は `fields`（項目名は camelCase）も返します。
- 互換パスのうち、メッセージの送信や配信ログの取得など Webhook Admin で提供していないものは、404 `not_found` を返します。送信とログの取得には `/v1` の API をご利用ください。

移行元との主な相違点は次のとおりです。

- トランスフォーメーションで変更できるのは URL のパスとクエリのみで、ホストは変更できません。
- トランスフォーメーションでは、`webhook-*`・`content-type` など配信に使うヘッダーは設定できません。
- Webhook 以外のエンドポイントには、メッセージを 1 件ずつ署名付きで配信します。
- 顧客ポータルではトランスフォーメーションを設定できません。送信側で設定してください。
- トランスフォーメーションで配信をキャンセルした場合、配信ログには失敗ではなく `cancelled` として記録されます。

## 一括再送信

エンドポイントの障害が復旧した後、そのエンドポイントへの配信が失敗で終わったものを、指定した時刻以降の分だけ一括で再送信します。同じメッセージ（同じ `webhook-id`）のまま配信するため、受信側は処理済みの ID を破棄できます。

- **管理画面**: エンドポイントの詳細
- **API**: `POST /v1/endpoints/{id}/recover`（[パラメーターとレスポンス](#recover-endpoint)）
- **MCP**: `recover_endpoint`・`get_recovery`
- **顧客ポータル**: 顧客が自身のエンドポイントについて実行できます

- キューで段階的に配信します（10 秒ごとに 50 通）。エンドポイントの同時接続数が上限に達している間は待機します。
- 対象は、開始時点で失敗となっていた配信です。開始後に失敗したものや、個別に再送信したものは含みません。
- 自動で無効化されていた（`disabled`）間のメッセージは、そのエンドポイント宛ての配信が作成されていないため対象外です。一時停止中（`paused`）の配信は保留されており、再開時に配信します。
- 実行中にエンドポイントを一時停止・無効化・削除すると、その時点で終了します（`stopped`）。
- 使用量には数えません。

## アラートの条件

アラート（Slack・Teams・Chatwork・メール・Webhook）を送る条件は、組織ごとに設定できます。設定は管理画面の「アラート」の「通知条件」、または MCP の `update_alert_conditions` で行います。条件はすべての通知チャネルで共通です。

| 条件 | 設定値 | 既定 |
| --- | --- | --- |
| 配信の失敗 | 初回の失敗の直後、または失敗が 5 分・15 分・60 分続いたとき | 60 分 |
| 自動無効化 | 失敗が 5 日間続いてエンドポイントを無効化したとき | — |
| 再送信待ちの件数 | 環境の再送信待ちが 100 通・1,000 通・10,000 通を超えたとき（1 日 1 回） | 1,000 通 |
| 使用量 | 今月の配信数が月間上限の 50%・80%・100% に達したとき | 80% |
| ポーリングされていないエンドポイント | ポーリングエンドポイントが 1・6・24・72 時間ポーリングされていないとき（再びポーリングされるまでに 1 回） | 24 時間 |

- 配信の失敗は、どの設定でも再送信回数を使い切った時点で通知します。同じエンドポイントのアラートは 1 時間に 1 回までです。
- 同じ組織への配信失敗のアラートは、1 時間に 1 通まで即時に送ります。その 1 時間内に失敗し始めた他のエンドポイントは、1 時間ごとに 1 通にまとめて送ります。
- 再送信待ちの件数は、エンドポイント単位ではなく環境全体の合計で判定します。
- 組織の owner には、この条件とは別に、使用量が月間上限の 80%・100% に達したときにメールで通知します。

## ソース（他サービスからの Webhook の受信）

Stripe・GitHub・Shopify・Slack・Meta・Zoom・LINE などのサービスが送信する Webhook を受信し、署名を検証したうえで、指定した顧客アカウント宛てのメッセージを作成します。作成したメッセージは `POST /v1/messages` で送信したメッセージと同じ扱いで、その顧客アカウントのエンドポイントに配信します。再送信・順序保証・トランスフォーメーション・固定送信元 IP・Webhook 以外のエンドポイント・アラート・一括再送信もそのまま利用できます。設定は管理画面・API・MCP で行います。

```
POST /v1/sources
{ "name": "Stripe", "provider": "stripe", "consumer_id": "con_…" }

HTTP/1.1 201 Created
{ "id": "src_…", "provider": "stripe", "status": "active",
  "url": "https://api.webhookadmin.com/in/…", "secret_hint": null, … }
```

- **受信 URL**: `https://api.webhookadmin.com/in/{token}`。ソースごとに発行し、送信元サービスの Webhook 設定に登録します（[レスポンス](#receive-webhook)）
- **配信先**: ソースごとに指定する顧客アカウント（`consumer_id`）。自社用の顧客アカウントを作成し、社内システムをエンドポイントとして登録する使い方が一般的です
- **受け付けるリクエスト**: POST・PUT・PATCH・DELETE（GET は Meta のサブスクリプション確認のみ）。ボディは 10MB までで、JSON・フォーム・テキスト・バイナリのいずれも受け付けます
- **署名シークレット**: 送信元サービスの管理画面からコピーして登録します。管理画面と API には、マスクしたヒント（`secret_hint`）のみを表示します。再登録後も 24 時間は、以前の署名シークレットを受け付けます
- **レート制限**: ソースごとの受信回数に上限は設けていません。乱用防止のため、1 つのソースに 10 秒間で 1,000 回を超えて届いたリクエストには、429 と `retry-after` を返します。署名を検証しないソース（`none`）は、API の 1 分あたりのレート制限の対象にも含めます
- **ソース数**: Free 1・Starter 5・Pro 20・Business 無制限（組織内のすべての環境の合計）

### Stripe

1. ソースを作成します（`provider: stripe`）。署名シークレットは後から登録できます。
2. ソースの受信 URL をコピーします。
3. Stripe ダッシュボードのワークベンチの「Webhook」タブで「イベント送信先を作成」を選択します。受信するイベントを選び、送信先のタイプに「Webhook エンドポイント」を選択して、エンドポイント URL に受信 URL を入力します。
4. 作成したエンドポイントのページで「シークレットを表示」を押し、署名シークレット（`whsec_…`）をコピーして、ソースの署名シークレットに登録します（管理画面または [署名シークレットの登録](#set-source-secret)）。

### GitHub

1. ソースを作成し（`provider: github`）、推測されにくい文字列を決めて署名シークレットに登録します。
2. リポジトリまたは組織の「Settings」›「Webhooks」›「Add webhook」で、Payload URL に受信 URL、Content type に `application/json`、Secret に同じ文字列を入力します。
3. 受信するイベントを選択して保存します。追加直後に届く `ping` も、イベントタイプ `ping` のメッセージとして受け付けます。

### Shopify

1. ソースを作成します（`provider: shopify`）。
2. アプリの Webhook サブスクリプション（`shopify.app.toml` または Admin API）で、Webhook の URL に受信 URL を指定します。ストアの管理画面の「設定」›「通知」›「Webhook」で作成する場合も同様で、形式は JSON を選択します。
3. 署名シークレットには、アプリで購読した Webhook の場合はアプリのクライアントシークレットを登録します。ストアの管理画面で作成した Webhook の場合は、同じ画面の Webhook 一覧の下に表示される署名キーを登録します。

### Slack

1. ソースを作成し（`provider: slack`）、Slack アプリの「Basic Information」›「App Credentials」にある「Signing Secret」を署名シークレットに登録します。
2. 「Event Subscriptions」を有効にし、Request URL に受信 URL を入力します。Slack が送信する確認リクエスト（`url_verification`）には自動で応答するため、そのまま確認済みになります。
3. 受信するイベントを選択して保存します。

### Meta（Facebook・WhatsApp・Instagram）

1. ソースを作成し（`provider: meta`）、アプリの「App settings」›「Basic」にある「App secret」を署名シークレットに登録します。サブスクリプションの確認に使う文字列を決め、`config.verify_token` に登録します。
2. アプリの Webhooks の設定（WhatsApp は「WhatsApp」›「Configuration」、WhatsApp のユースケースで作成したアプリは「Use cases」›「Customize」›「Configuration」）で、コールバック URL に受信 URL、認証トークン（Verify token）に同じ文字列を入力します。
3. Meta が送信するサブスクリプションの確認（GET の `hub.mode=subscribe`）には、`hub.verify_token` が `config.verify_token` と一致すれば `hub.challenge` を text/plain で返します。一致しない場合と未設定の場合は 403 を返します。
4. 受信するフィールドを購読します。イベントタイプは、ペイロードの `object` と `entry[0].changes[0].field` を連結したものです（例 `whatsapp_business_account.messages`）。

```
{
  "provider": "meta",
  "config": { "verify_token": "…" }
}
```

### Zoom

1. ソースを作成し（`provider: zoom`）、Zoom App Marketplace のアプリの「Features」›「Access」に表示される「Secret Token」を署名シークレットに登録します。
2. 同じ画面で「Event Subscription」を有効にし、「Add New Event Subscription」の「Event notification endpoint URL」に受信 URL を入力して「Validate」を押します。Zoom が送信する確認リクエスト（`endpoint.url_validation`）には、`plainToken` と `encryptedToken` で自動で応答します。
3. 受信するイベントを選択して保存します。イベントタイプはペイロードの `event` です（例 `meeting.started`）。

### LINE

1. ソースを作成し（`provider: hmac`）、下記の `config` を指定します。LINE Developers コンソールのチャネルの「チャネル基本設定」タブにある「チャネルシークレット」を署名シークレットに登録します。
2. チャネルの「Messaging API設定」タブで、Webhook URL に受信 URL を入力し、「Webhookの利用」をオンにします。
3. 「検証」を押すと届くリクエスト（`events` が空）は、イベントタイプ `inbound` のメッセージとして受け付けます。

```
{
  "provider": "hmac",
  "config": {
    "header": "x-line-signature",
    "encoding": "base64",
    "event_type_field": "events.0.type"
  }
}
```

### Standard Webhooks

Standard Webhooks 形式（`webhook-id`・`webhook-timestamp`・`webhook-signature`）で署名するサービスは、`provider: standard` で受け付けます。Svix で送信するサービスの `svix-id`・`svix-timestamp`・`svix-signature` も同じ形式として受け付けます。OpenAI・Replicate は `webhook-`、Clerk・Resend は `svix-` のヘッダーで送信します。署名シークレットには、送信元サービスの署名シークレット（`whsec_…`）をそのまま登録します。

### 独自の HMAC 署名

ボディの HMAC-SHA256 をヘッダーに付けて送信するサービスは、`provider: hmac` で受け付けます。署名シークレットには送信元サービスと共有している文字列をそのまま登録し、ヘッダー名などを `config` で指定します（[ソースの設定](#source-config)）。

```
{
  "provider": "hmac",
  "config": {
    "header": "x-signature",
    "encoding": "hex",
    "prefix": "sha256=",
    "event_type_field": "event",
    "id_header": "x-delivery-id"
  }
}
```

### Basic 認証

Basic 認証のユーザー名とパスワードを付けて送信するサービスは、`provider: basic` で受け付けます。ユーザー名を `config.username` に、パスワードを署名シークレットに登録します。一致しない場合は 401 と `WWW-Authenticate: Basic` を返します。

```
{
  "provider": "basic",
  "config": { "username": "webhook" }
}
```

### API キー

API キーをヘッダーまたはクエリに付けて送信するサービスは、`provider: api_key` で受け付けます。キーの値を署名シークレットに登録します。ヘッダーの場合は `config.header`（値の前に付く文字列があれば `config.prefix`。例 `"Bearer "`）、クエリの場合は `config.query` にその名前を指定します。

```
{
  "provider": "api_key",
  "config": { "header": "authorization", "prefix": "Bearer " }
}
```

署名を付けないサービスは、`provider: none` で受け付けます。この場合、受信 URL を知っていることが唯一の認証手段になります。

### ソースの設定

| 項目 | 対象 | 説明 |
| --- | --- | --- |
| `header` | `hmac`・`api_key` | 署名ヘッダー名（`hmac` では必須）。`api_key` ではキーのヘッダー名 |
| `query` | `api_key` | キーのクエリパラメーター名（`header` または `query` のいずれか） |
| `encoding` | `hmac` | `hex`（既定）または `base64` |
| `prefix` | `hmac`・`api_key` | 値の前に付く文字列（例 `sha256=`・`"Bearer "`）。除去してから比較します |
| `username` | `basic` | ユーザー名（必須。パスワードは署名シークレットに登録） |
| `verify_token` | `meta` | サブスクリプション確認で `hub.verify_token` と比較する文字列 |
| `event_type_field` | `hmac`・`basic`・`api_key`・`none` | イベントタイプを読み取るペイロードの項目。入れ子は `.` で区切り、配列はインデックスで指定（例 `data.type`・`events.0.type`） |
| `id_header` | `hmac`・`basic`・`api_key`・`none` | 重複判定に使うヘッダー（送信元サービスの配信 ID） |
| `event_type` | すべて | イベントタイプを取得できない場合のイベントタイプ（既定 `inbound`） |
| `forward_headers` | すべて | エンドポイントへ引き渡す受信時のヘッダー（20 個まで。[受信ボディの転送](#source-raw)） |
| `response` | すべて | 受付時のレスポンス（[受付時のレスポンス](#source-response)） |

### 署名の検証

| `provider` | 検証するヘッダー | 時刻 |
| --- | --- | --- |
| `stripe` | `Stripe-Signature` | 5 分以内 |
| `github` | `X-Hub-Signature-256` | — |
| `shopify` | `X-Shopify-Hmac-Sha256` | — |
| `slack` | `X-Slack-Signature`・`X-Slack-Request-Timestamp` | 5 分以内 |
| `meta` | `X-Hub-Signature-256`（シークレットはアプリの App secret） | — |
| `zoom` | `x-zm-signature`・`x-zm-request-timestamp` | 5 分以内 |
| `standard` | `webhook-id`・`webhook-timestamp`・`webhook-signature`（または `svix-id`・`svix-timestamp`・`svix-signature`） | 5 分以内 |
| `hmac` | `config.header` で指定したヘッダー | — |
| `basic` | `Authorization`（`Basic …`） | — |
| `api_key` | `config.header` のヘッダーまたは `config.query` のクエリ | — |

受信したボディのバイト列から計算し、定数時間で比較します。一致しない場合は 401 で拒否し、受信ログに理由を記録します。

次の URL 確認リクエストには、署名を検証したうえで各サービスの形式で応答します。メッセージは作成せず、月間配信数にも数えません。一時停止中も応答します。

- Slack の `url_verification`: `{ "challenge": "…" }`
- Zoom の `endpoint.url_validation`: `{ "plainToken": "…", "encryptedToken": "…" }`
- Meta のサブスクリプション確認（GET）: `hub.challenge` の値（text/plain）

### イベントタイプと重複判定

| `provider` | イベントタイプ | 重複判定 |
| --- | --- | --- |
| `stripe` | ペイロードの `type`（例 `invoice.paid`） | ペイロードの `id`（`evt_…`） |
| `github` | `X-GitHub-Event` とペイロードの `action` を連結したもの（例 `pull_request.opened`） | `X-GitHub-Delivery` |
| `shopify` | `X-Shopify-Topic`（`orders/create` は `orders.create`） | `X-Shopify-Webhook-Id` |
| `slack` | ペイロードの `event.type`（無ければ `type`） | ペイロードの `event_id` |
| `meta` | ペイロードの `object` と `entry[0].changes[0].field` を連結したもの（`field` が無ければ `object`） | ペイロードの内容（同じペイロードの再送信を重複とみなす） |
| `zoom` | ペイロードの `event` | ペイロードの内容（同じペイロードの再送信を重複とみなす） |
| `standard` | ペイロードの `type` | `webhook-id`（または `svix-id`） |
| `hmac`・`basic`・`api_key`・`none` | `config.event_type_field` の項目 | `config.id_header` のヘッダー |

- イベントタイプを取得できない場合は `config.event_type`（既定 `inbound`）を使います。`/` は `.` に、使用できない文字は `_` に置き換え、100 文字までに切り詰めます。
- 同じ配信 ID のリクエストには、24 時間は最初のメッセージの ID を返します。送信元サービスによる再送信は 1 通のメッセージにまとめ、受信ログでは `duplicate` になります。
- JSON のボディは、その値をメッセージの `data` にします。フォームやテキストのボディは `{ "content_type": "…", "body": "<ボディの文字列>" }` にします。
- 受信したヘッダーは受信ログで確認できます。エンドポイントへの配信には、そのエンドポイントの署名シークレットで生成した署名を付けます。

### 受付時のレスポンス

受付時（重複・保留を含む）のレスポンスは、既定で 200 と `{ "id": "msg_…" }`（保留の場合は `{ "status": "held", "request_id": "sreq_…" }`）です。レスポンスボディを読み取るサービス（Twilio の TwiML など）向けには、ソースごとに `config.response` でステータスコード・Content-Type・ボディを指定します。

```
"config": {
  "response": {
    "status": 200,
    "content_type": "application/xml",
    "body": "<Response></Response>"
  }
}
```

| 項目 | 説明 |
| --- | --- |
| `status` | 200〜299（既定 200）。204・205 はボディを空にして返します |
| `content_type` | 既定値は `text/plain; charset=utf-8` |
| `body` | 4KB まで（既定は空） |

URL 確認リクエストへの応答と、拒否時のレスポンスは変わりません。

### 受信ボディの転送

エンドポイントのペイロード形式（`body_format`）が `raw` の場合、ソースで受信したメッセージは、受信したボディをバイト列のまま、受信時の Content-Type で配信します（Content-Type が無い場合は `application/octet-stream`）。JSON の空白やキーの順序、フォーム、UTF-8 以外の文字コードのボディも変わりません。Webhook Admin の署名（`webhook-id`・`webhook-timestamp`・`webhook-signature`）は、配信するバイト列から生成します。`standard` のエンドポイントには、従来どおり `{ type, timestamp, data }` を配信します。

`config.forward_headers` に指定したヘッダーは、受信時の値のままエンドポイントへ引き渡します（20 個まで、小文字で指定）。`raw` のエンドポイントと組み合わせると、受信側で送信元サービスの署名（`stripe-signature` など）も検証できます。

```
"config": {
  "forward_headers": ["stripe-signature"]
}
```

- Webhook Admin が付与するヘッダー（署名・Content-Type・User-Agent）と、トランスフォーメーションで追加したヘッダーが優先されます。
- 認証・Cookie・ボディと接続に関わるヘッダーや、`webhook-` で始まるヘッダーなど、配信に使うヘッダーは指定できません（422）。
- トランスフォーメーションには、従来どおり解析済みのペイロード（`webhook.payload`）を渡します。トランスフォーメーションでペイロードを変更した場合は変更後の JSON を、URL やヘッダーのみを変更した場合は受信したボディをそのまま配信します。
- トランスフォーメーションに渡せるペイロードは 1MB までです。1MB を超えるペイロードの配信は、トランスフォーメーションを適用せずに失敗となり、再送信しません（配信試行の `error` は `transformation: input_too_large: …`）。トランスフォーメーションを解除または修正してから、再送信してください。
- Webhook 以外のエンドポイントには、UTF-8 として読めるボディはその文字列を、読めないボディは `data` の JSON を配信します。ポーリングエンドポイントは、従来どおり `data` の JSON です。
- 受信したボディがログ保存期間を過ぎて残っていない場合は、`data` の JSON を配信します。

### 一時停止

一時停止中のソースは、署名を検証したうえでリクエストを受け付けて保留し、送信元サービスには 200（`{ "status": "held", "request_id": "sreq_…" }`。[受付時のレスポンス](#source-response) を設定した場合はそのレスポンス）を返します。送信元サービスでは失敗として扱われません。そのため、失敗時に自動で再送信しないサービスや、失敗が続くとサブスクリプションを停止するサービスでも、一時停止中の Webhook を取りこぼしません。

- 再開（`status: active`）すると、保留したリクエストを受信順にメッセージにして配信します。重複判定は受付時と同じです。
- 再開後に届いたリクエストは直ちに処理するため、保留分の処理が終わるまでは、保留したリクエストと順序が前後することがあります。保留したリクエスト同士は受信順です。
- 保留件数は、ソースの `held` と、管理画面のソースの一覧・詳細で確認できます。受信ログでは `held` と表示されます。
- 保留したリクエストは、処理した時点で月間配信数に数えます。処理時に月間上限を超えていたリクエストは拒否となり、[再処理](#source-reprocess) できます。
- プランのログ保存期間を過ぎた保留分は削除されます。

受信は続けたままエンドポイントへの配信のみを停止する場合は、顧客アカウントのエンドポイントを一時停止します。保留したメッセージは再開時に配信します。

### 再処理

拒否したリクエストは、署名シークレットや設定を修正した後に再処理できます（[拒否したリクエストの再処理](#reprocess-source-request)・管理画面の受信ログの「再処理」・MCP の `reprocess_source_request`）。現在の署名シークレットと設定で再検証し、成功すれば受信 URL に届いた場合と同じ流れで処理します。

- 署名の時刻は受信時刻で検証します。署名の時刻から 5 分以内に受信していれば、署名シークレットを後から登録しても検証に成功します。
- 再処理できるのは、ボディが保存されている拒否済みのリクエスト（`reprocessable: true`）です。ボディが 10MB を超えて拒否したリクエストと GET のリクエストは対象外です（409）。
- 受け付けたメッセージは月間配信数に数えます。一時停止中のソースでは保留になります。

### 月間配信数とログ

- 受け付けた 1 通を、月間配信数の 1 通として数えます。重複・拒否したリクエスト・URL 確認リクエストと、エンドポイントが無く配信しないメッセージ（`no_endpoint`）は対象外です。テスト環境のソースは、テスト環境の 1 日の上限に数えます。
- 受信したリクエストはソースごとの受信ログに記録され、プランのログ保存期間中は確認できます（[ソースの受信ログ](#list-source-requests)）。拒否したリクエストの記録は、ソースごとに 1 分あたり 60 件までです。
- ソースから作成したメッセージは、メッセージの詳細の `source` に、ソースの ID・名前と受信ログの ID（`request_id`）が入ります。
- プランのダウングレードでソース数が上限を超えた場合は、作成順で上限を超えた分のソースが 402 を返します。ソースを削除するか、プランを戻すと受付を再開します。

## 型

```
MessageRow    = { id, event_type, consumer: { id, external_id, name }, created_at,
                  status: 'success' | 'retrying' | 'failed' | 'pending' | 'cancelled' | 'no_endpoint',
                  attempts, endpoint_url }
MessageDetail = { id, event_type, consumer, created_at, idempotency_key, payload, deliveries: Delivery[],
                  source: { id, name, request_id } | null }
Delivery      = { id, endpoint_id, url, status: 'pending' | 'success' | 'retrying' | 'failed' | 'cancelled' | 'waiting',
                  attempt_count, next_attempt_at, attempts: AttemptRow[],
                  endpoint_status: 'active' | 'paused' | 'disabled' | null }
AttemptRow    = { id, at, response_status, response_head, duration_ms, error,
                  via: 'direct' | 'relay' | 'poll' | DestinationType, source_ip,
                  transform: 'changed' | 'error' | null, ref: string | null }
                /* error: null | 'timeout' | 'connect_failed' | 'dns_failed' | 'blocked_ip' | 'blocked_url: …'
                   | 'relay_failed' (fixed IP) | 'circuit_open' | 'wait_limit' | 'transformation: <kind>[: <detail>]'
                   | the destination's own reason (e.g. 'kafka: …') */
Consumer      = { id, external_id, name, created_at, endpoint_count }
Endpoint      = { id, consumer_id, url: string | null, description, event_types: string[] | null, fixed_ip,
                  status: 'active' | 'paused' | 'disabled', disabled_reason, failing_since, created_at,
                  retry: RetryPolicy, compat_signature: CompatSignature | null,
                  ordering: 'none' | 'fifo', transformation_updated_at: number | null,
                  type: 'http' | 'polling' | DestinationType, destination: object | null,
                  last_polled_at: number | null, body_format: 'standard' | 'raw' }
DestinationType = 'sqs' | 'eventbridge' | 'pubsub' | 's3' | 'r2' | 'gcs' | 'azure_blob'
                | 'servicebus' | 'kafka' | 'rabbitmq' | 'slack' | 'teams' | 'chatwork' | 'google_chat'
RetryPolicy   = { count: 0 | 1 | 2 | 3 | 5 | 7 | 12,
                  interval: 'progressive' | '1m' | '5m' | '30m' | '1h' | '6h' }
CompatSignature = { header, content: 'body' | 'timestamp_body', encoding: 'hex' | 'base64', prefix }
Recovery      = { id, endpoint_id, since, status: 'running' | 'done' | 'stopped',
                  total, queued, created_at, finished_at }
EventType     = { name, description, archived_at }
PollerToken   = { id, endpoint_id, name, prefix, created_at, last_used_at }
Transformation = { endpoint_id, code, updated_at, enabled: boolean,
                   variables: Record<string, string> | null }
PolledMessage = { id, event_type, timestamp, payload /* body_format: raw なら data */,
                  headers: { 'webhook-id', 'webhook-timestamp', 'webhook-signature' } }
Source        = { id, name, provider: SourceProvider, consumer_id, consumer: { id, external_id, name },
                  status: 'active' | 'paused', url, secret_hint: string | null, config: SourceConfig,
                  held, created_at, updated_at }
SourceProvider = 'stripe' | 'github' | 'shopify' | 'slack' | 'meta' | 'zoom' | 'standard'
               | 'hmac' | 'basic' | 'api_key' | 'none'
SourceConfig  = { event_type?, event_type_field?, id_header?, header?, query?, algorithm?: 'sha256',
                  encoding?: 'hex' | 'base64', prefix?, verify_token?, username?,
                  forward_headers?: string[], response?: { status, content_type, body } }
SourceRequest = { id, at, method: 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'GET',
                  status: 'accepted' | 'duplicate' | 'rejected' | 'held', response_status, reason,
                  message_id, event_type, content_type, size, headers: Record<string, string>,
                  processed_at: number | null, reprocessable: boolean }
```
