イベントと Webhook
送信ログでメッセージのステータスを追跡し、署名付きの配信、バウンス、苦情、開封、クリックの各イベントをお客様のエンドポイントで受信します。
送信したメッセージにはそれぞれメッセージ ID が付与され、キューへの登録、配信、再試行、バウンスに応じて一連のステータスを経て処理されます。この状況は、コンソールの送信ログで確認したり、API で照会したり、Webhook で各イベントをお客様自身のサーバーに送信させたりできます。
メッセージのステータス
受信者ごとのコピーにはそれぞれ 1 つのステータスがあります。ステータスは先に進むだけです。たとえば、配信済みのメッセージが後からバウンスや苦情になることはありますが、キュー済みに戻ることはありません。
| ステータス | コンソールの表示 | 意味 |
|---|---|---|
queued | 待機中 | 受け付けられ、配信を待っています |
deferred | 遅延・再試行 | 受信側サーバーが一時エラーを返しました。配信は最大 72 時間再試行されます。 |
delivered | 配信済み | 受信側サーバーがメッセージを受け付けました |
bounced | バウンス | 配信中、または後から受け取ったバウンスレポートにより、配信が恒久的に失敗しました。バウンスの種類は下記をご覧ください。 |
complained | 苦情 | 受信者がメッセージをスパムとして報告しました |
rejected | 拒否済み | 送信前に拒否されました。たとえば受信者が抑制されている、サンドボックスで許可されていない、コンテンツが拒否された、などの場合です |
failed | 失敗 | 72 時間再試行しても配信できなかった、またはキューに入れられませんでした |
開封、クリック、配信停止はステータスではありません。これらはメッセージに記録され(opened_at、clicked_at、unsubscribed_at)、Webhook イベントとして送信されます。送信ログでは、開封されたメッセージに 開封済みタグが追加で表示されます。
バウンスの種類
| 種類 | 発生条件 | 影響 |
|---|---|---|
| ハード | 不明なユーザーなどによる恒久的な 5xx 拒否 | アドレスがグローバル抑制リストに追加されます |
| ソフト | メールボックスの容量超過(x.2.2)、またはシステム、ネットワーク、ポリシーによる拒否(x.3.x〜x.7.x。IP レピュテーションによるブロックなど) | アドレスは抑制されません |
送信ログ
ストリームの 送信ログタブには、過去 30 日間のメッセージが、日時、受信者、件名、カテゴリ、送信元(API または SMTP)、ステータスとともに一覧表示されます。受信者、ステータス、取得元、カテゴリ、日付範囲で絞り込むことができます。
メッセージをクリックすると メッセージの詳細が開き、ステータス、送信者、件名、カテゴリ、タグ、受信側サーバーの最後の応答、そして各配信試行の SMTP コード、応答、バウンスの種類、MX ホスト、IP を含むイベント一覧が表示されます。
概要タブでは、同じデータを 7 日間または 30 日間で集計して表示します:送信数、配信率、バウンス率、苦情率、開封数、クリック数。
Webhook
Webhook は、各イベントが発生した時点でお客様の HTTPS エンドポイントに送信します。これにより、たとえばハードバウンスの後にユーザーのアドレスを無効としてマークする、といった処理をアプリケーションで行えます。
Webhook を設定する
- ストリームを開き、Webhook タブに移動します。
- 受信 URLに、インターネットからアクセスできる
https://URL を入力します。プライベートアドレスやローカルアドレスは使用できません。 - イベントで、受信するイベントを選択します。既定ではすべてのイベントが選択されています。
- 保存をクリックします。初めて URL を保存したときに、署名シークレットが一度だけ表示されます。
whsec_で始まります。サーバー上に保管してください。 - テスト送信をクリックして
testイベントを送信し、エンドポイントが応答することを確認します。
新しいシークレットを取得するには、署名シークレットをリセットをクリックします。古いシークレットは直ちに無効になります。イベントの受信を停止するには、URL を消去して保存します。
イベント
| イベント | コンソールの表示 | 送信されるタイミング |
|---|---|---|
delivered | 配信済み | 受信側サーバーがメッセージを受け付けたとき |
deferred | 遅延(tarpit) | 配信試行が一時的に失敗したとき。試行ごとに 1 回送信されます。 |
bounced | バウンス | 恒久的な失敗。data.bounce_type は hard または soft です。 |
complained | 苦情 | 受信者がメッセージをスパムとして報告したとき |
failed | 失敗 | 72 時間後に配信が断念されたとき |
rejected | 拒否 | 送信サーバーが受信者を拒否したとき |
opened | 開く | メッセージが初めて開封されたとき(開封トラッキングが必要) |
clicked | クリック | トラッキング対象のリンクがクリックされるたび(クリックトラッキングが必要) |
unsubscribed | 配信停止 | 受信者が配信を停止したとき |
HTTP API 自体が拒否した受信者は API レスポンスの rejected フィールドで返され、Webhook は発生しません。
リクエストの形式
各イベントは、JSON 本文を持つ POST として送信されます。
POST /your/webhook HTTP/1.1
Content-Type: application/json
User-Agent: Yeap-TM-Webhook/1
X-Yeap-Signature: t=1760955304,v1=5f2b9c...e81a
{
"id": "evt_01JAQ40B2C3D4E5F6G7H8J9K0M",
"type": "bounced",
"stream_id": 12,
"message_id": "01JAQ3Z9V8K4M2N6P7R5S1T0XW",
"created_at": "2026-10-20T10:15:04+00:00",
"data": {
"rcpt": "alice@example.org",
"smtp_code": 550,
"smtp_response": "5.1.1 The email account that you tried to reach does not exist",
"bounce_type": "hard",
"mx": "mx.example.org"
}
}id はイベントごとに一意です。重複を無視するために使用してください。data の内容はイベントによって異なります。
| イベント | data のフィールド |
|---|---|
delivered, deferred, bounced, complained, failed, rejected | rcpt、および利用可能な場合は smtp_code、smtp_response、bounce_type、mx、at |
opened | rcpt、ip、user_agent |
clicked | rcpt、url、ip、user_agent |
unsubscribed | rcpt、category、via |
test | message |
署名を検証する
X-Yeap-Signature ヘッダーは t=<timestamp>,v1=<signature> の形式です。署名は、署名シークレット全体(whsec_ を含む)をキーとして <timestamp>.<raw request body> から計算した HMAC-SHA256 の 16 進文字列です。リクエストを確認するには、次の手順に従います。
- JSON を解析する前に、受信した生の本文をそのまま読み取ります。
t + "." + bodyに対して HMAC を計算し、定数時間比較でv1と比較します。- リプレイ攻撃を防ぐため、タイムスタンプが 5 分より古いリクエストは拒否します。
Python:
import hashlib, hmac, time
def verify(secret: str, header: str, body: bytes, tolerance: int = 300) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
ts, sig = parts.get("t", ""), parts.get("v1", "")
if not ts.isdigit() or abs(time.time() - int(ts)) > tolerance:
return False
expected = hmac.new(secret.encode(), ts.encode() + b"." + body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, sig)Node.js:
const crypto = require('crypto');
function verify(secret, header, rawBody, tolerance = 300) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const ts = Number(parts.t);
if (!ts || Math.abs(Date.now() / 1000 - ts) > tolerance) return false;
const expected = crypto.createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(parts.v1 || '');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}応答と再試行
- 10 秒以内に任意の
2xxステータスで応答してください。それ以外はすべて失敗とみなされます。 - 失敗した送信は、間隔を広げながら再試行されます。間隔は 1 分から始まり、試行間で最大 4 時間まで延びます。再試行はイベント発生から 24 時間後に停止します。
- イベントは複数回届いたり、順序が前後したりすることがあります。重複のスキップにはイベントの
idを、現在の状態の判断にはメッセージのステータスを使用してください。
Webhook タブの 最近の配信表には、直近 100 件の送信が、イベント、ステータス(配信待ち 保留中、成功 成功、再試行中 再試行中、中止 断念)、試行回数、最後のエラー、次回の試行日時とともに表示されます。