Webhook
API キーを取得Webhook を登録する
公開された https のアドレスと、受け取りたいイベントを登録します。署名シークレット(whsec_…)はこの応答でのみ表示されます。1 つのアプリケーションに登録できる webhook は最大 10 件です。
curl -X POST https://api.sofia-post.com/api/v1/webhooks \
-H "Authorization: Bearer $SOFIA_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"url":"https://app.example.com/hooks/sofia","events":["post.published","post.failed"]}'イベント
| イベント | data |
|---|---|
post.published | postId, establishmentId, platform, publishedAt, permalink |
post.failed | postId, establishmentId, platform, error |
webhook.test | webhookId |
Sofia は各イベントを JSON { id, type, createdAt, data } として POST し、Sofia-Event-Id、Sofia-Event-Type、Sofia-Signature のヘッダーを付けます。通知されるのは、あなたのアプリケーションが作成した投稿のみです。
署名を検証する
Sofia-Signature は t=<unix time>,v1=<hex> の形式で、v1 は <t>.<raw body> をシークレットで計算した HMAC-SHA256 です。生のボディで検証し、古いタイムスタンプは拒否してください。
import { createHmac, timingSafeEqual } from 'node:crypto';
const isSignedBySofia = (header, rawBody, secret) => {
const parts = Object.fromEntries(
(header ?? '').split(',').map((part) => part.trim().split('=', 2)),
);
const timestamp = Number(parts.t);
if (!Number.isInteger(timestamp) || typeof parts.v1 !== 'string') return false;
if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
const expected = createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest();
const received = Buffer.from(parts.v1, 'hex');
return received.length === expected.length && timingSafeEqual(received, expected);
};配信と再試行
2xxの応答でイベントは配信済みとなります。それ以外の応答、リダイレクト(追従しません)、10 秒間応答がない場合は、1 分後、5 分後、30 分後、2 時間後に再試行され、最大 5 回です。- 同じイベントが 2 回届くことがあります。
Sofia-Event-Idで重複を排除してください。 - すばやく応答し、イベントの処理はその後に行ってください。
POST /v1/webhooks/{id}/test は、他のイベントと同様に署名された webhook.test イベントを送信します。