メインコンテンツへスキップ
Certyneo
開発者資料

ウェブフック リアルタイムでサインアップイベントを受信

CertyneoダッシュボードにHTTPS URLを設定すると、エンベロープでイベント(署名、拒否、有効期限切れ)が発生するたびにHMAC-SHA256で署名されたPOSTを受け取ります。11のイベントをサポート、指数バックオフで5回の配信試行、8行のコードで暗号化検証が可能です。

< 5s

事件後の平均的な配達時間

5x

合計配信試行回数、約1時間20分にわたって実施

HMAC-SHA256

要求の各項の署名アルゴリズム

事件カタログ

以下の12のイベントは、Certyneoエンベロープのライフサイクル全体をカバーしています。設定 → Webhooksで必要なイベントを有効にし、その他は無視してください — サブスクリプションはイベント単位のきめ細かい設定です。

事件発症
envelope.created作成時に CRM側で記録を同期するのに役立つエンベラップが作成されます.
envelope.sent封筒は署名者に送られる (最初のメール送信). 署名サイクルが始まることを意味します.
envelope.completedすべての署名者が署名を完了し、eIDASシール済みPDFが保存されました。ペイロードには7日間有効な事前署名付きリンク「signedDocumentUrl」が含まれます。または GET /v1/envelopes/id/signed-document で取得し、監査証跡は GET /v1/envelopes/id/audit-trail で確認できます。
envelope.declined署名者がエンベロープを拒否しました。拒否者のアドレスは `data.declinedBy` に、入力された場合は理由は `data.reason` に含まれます。
envelope.voided封筒は発行者が署名が完了する前にキャンセルされました. `expired` (human vs timeout) とは異なります.
envelope.expiredエンベロープの有効期限が切れており、署名が完了していません。GET /v1/envelopes/id で署名が必要なのはどの署名者かを確認してください。
envelope.returned_to_sender署名者がエンベロープを発行者に修正のため返却しました。拒否ではありません。理由は`data.reason`に、返却者は`data.returnedBy`に含まれています。
envelope.resubmitted発行者が以前に返却されたエンベロープを修正して再送信しました。署名サイクルの再開をマークします。
recipient.signed個別の署名者が署名しました(ただし、必ずしもすべてではありません)。進捗状況を追跡し、シーケンシャルワークフローの次のステップに進めるのに役立ちます。注意:この段階では、最後の署名者であっても、シールされたPDFはまだ存在しません。ここで開始されたダウンロードはHTTP 409を返します。ドキュメントについては、envelope.completedを使用してください。
recipient.viewed署名者が署名しなくても リンクを開けました 標的型再開で便利です
recipient.approved承認者が署名を付けずにエンベロープを承認しました(内部検証ワークフロー)。アドレスは`data.approvedBy`に含まれています。
recipient.bounced受取人のメールサーバーが招待状またはリトライを恒久的に拒否しました(メールボックスが存在しないか、ドメインが無効です)。受取人のステータスはBOUNCEDに変更され、自動リトライが停止されます。アドレスは`data.recipientEmail`に含まれています。アドレスを修正してエンベロープを再送信してください。

recipient.signedは「ドキュメント利用可能」を意味しません

recipient.signedは各署名者に対して、署名を完了した時点で発行されます。最後の署名者も含め、シールされたPDFがアセンブルおよび保存される前です。このハンドラーからトリガーされたダウンロードは、常にHTTP 409「Signed document not available until the envelope is COMPLETED」を受け取ります。これはエラーではなく、「まだ準備ができていない」状態です。envelope.completedにサブスクライブしてドキュメントを取得し、recipient.signedを使用して進捗状況を追跡してください(誰が署名し、いつ署名したか)。

積載物の格式

すべての配信は同じトップレベルJSONスキーマを共有しています:`event`、`data`、`timestamp`。イベント名は`X-Certyneo-Event`ヘッダーにも含まれており、ボディを解析する前にルーティングできます。`data`の内容はイベントによって異なりますが、常に単純な値のフラットオブジェクトです。配列やネストされたオブジェクトはありません。次は完全な`envelope.completed`配信です。

POST /webhooks/certyneo HTTP/1.1
Host: your.app
Content-Type: application/json
X-Certyneo-Event: envelope.completed
X-Certyneo-Delivery-Id: cm8f2h6a10004qr9k5p2wm3xt
X-Certyneo-Signature: 4f3d1c8b2a9e7f60d5c4b3a2918e7f6d5c4b3a2918e7f6d5c4b3a2918e7f6d5c
{
  "id": "cm8f2h6a10004qr9k5p2wm3xt",
  "event": "envelope.completed",
  "data": {
    "envelopeId": "cm7x2k9p40001qz8h3f7bn2ld",
    "sandbox": false,
    "subject": "Contrat de prestation Acme Corp",
    "status": "COMPLETED",
    "signatureLevel": "ADVANCED",
    "aesSignerCount": 2,
    "completedAt": "2026-05-27T08:42:13.000Z",
    "recipientCount": 2,
    "recipients": [
      { "email": "alice@acme.com", "name": "Alice Martin", "role": "SIGNER", "status": "SIGNED" },
      { "email": "bob@acme.com", "name": "Bob Durand", "role": "SIGNER", "status": "SIGNED" }
    ],
    "signedDocumentUrl": "https://storage.certyneo.com/signed/cm7x2k9p4.../contrat.pdf?X-Amz-Expires=604800&X-Amz-Signature=..."
  },
  "timestamp": "2026-05-27T08:42:13.521Z"
}

signedDocumentUrlは、イベントから7日間有効な事前署名されたリンクです。認証されたAPIコールなしでPDFを取得できるため、2番目のコールを避けられます。事前署名に失敗した場合、またはドキュメントが保存されていないQES完了エンベロープ上の場合は、存在しません(nullではありません)。その場合は、GET /v1/envelopes/id/signed-documentを使用してください。これが信頼できるソースです。また、イベントから7日以上後に配信不可能キューから手動で再生した場合、ペイロード内のリンクは期限切れですが、APIエンドポイントはそうではないことに注意してください。

payload は UTF-8 でエンコードされ,BOM は含まれていない. HMAC 署名は送信されたままのボディ・ブルートに計算される. 空間を改変しないでください. JSON 再解析はしばしばキー順序を変更し,検証を破ります.

HMACの署名を確認する

各リクエストはWebhookシークレット(サブスクリプション作成時に1回だけ表示される)で署名されています。署名は`X-Certyneo-Signature`ヘッダーで送信されます。これは要求本文のHMAC-SHA256で、16進法でエンコードされており、接頭辞やタイムスタンプはありません。ペイロードを処理する前に、常に署名を検証してください。この検証なしに、誰でもイベントを偽造してエンドポイントを呼び出すことができます。

Node.js / TypeScript

import crypto from "node:crypto";

export function verifyCertyneoSignature(
  rawBody: string,
  signatureHeader: string,
  secret: string,
): boolean {
  // X-Certyneo-Signature = HMAC-SHA256(secret, rawBody), hex-encoded.
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");

  const received = Buffer.from(signatureHeader, "hex");
  const computed = Buffer.from(expected, "hex");

  // timingSafeEqual throws when the two buffers differ in length —
  // a malformed header must return false, not crash the handler.
  if (received.length !== computed.length) return false;

  // timingSafeEqual to mitigate timing attacks.
  return crypto.timingSafeEqual(computed, received);
}

Python

import hashlib
import hmac


def verify_certyneo_signature(
    raw_body: bytes, signature_header: str, secret: str
) -> bool:
    """Verify a Certyneo webhook signature.

    `X-Certyneo-Signature` holds the hex-encoded HMAC-SHA256 of the
    raw request body, computed with your webhook secret.
    """
    expected = hmac.new(
        secret.encode("utf-8"),
        raw_body,
        hashlib.sha256,
    ).hexdigest()

    return hmac.compare_digest(expected, signature_header)

よくある間違い

期待された署名と受信された署名を比較するために, `===`または `==`を使用しないでください. タイムセーフ関数 (`crypto.timingSafeEqual` in Node, `hmac.compare_digest` in Python) を使用してください. そうでなければ,二つの署名間の比較時間の差が,徐々に秘密を患者攻撃者に明らかにします.

試行錯誤の方針

エンドポイントの応答に時間がかかる場合、接続を拒否する場合、または5xx(または429)を返す場合、指数バックオフに従って再試行します:合計5回の試行、約1時間20分間。5番目の試行後、イベントはデッドレターキューに送られます—ダッシュボードから確認および再生可能ですが、自動再試行は行われません。明示的な拒否(401、403、404、410、422など)は再試行されません:応答は変わらないため、イベントはデッドレターキューに直接送られます。

試行錯誤試行前の期間イベントから経過した時間
#100
#2+ 1 min1 min
#3+ 5 min6 min
#4+ 15 min21 min
#5+ 1 h1 h 21

表示されている期間は最小値です:再試行スキャンはcronによって制御されるため、試行は理論上の時刻の直後に開始される場合があります。放棄されたイベントはWebhooks→失敗にリストされ、手動再生ボタンがあり、保持期間に制限はありません。5つの決定的な失敗が連続したエンドポイント、または404/410で応答したエンドポイントは自動的に無効化され、メールで通知されます:修正後に再度有効化するとカウンターがゼロにリセットされます(配信に成功するたびにもゼロにリセットされます)。

試しに送るのに 本物の封筒を送らない

まずサブスクリプションを作成してください。応答にはHMAC検証に必要な`secret`が含まれており、1回だけ表示されます。設定→Webhooksから、「テスト」ボタンは実際の配信とまったく同じように署名されたPOSTを送信し、サーバーの生の応答を表示します。これはハンドラーを検証する最速の方法で、ngrok経由でローカルに、または継続的インテグレーション環境で実行できます。

# Create a subscription — "secret" is returned once, store it now.
curl -X POST https://api.certyneo.com/v1/webhooks \
  -H "Authorization: Bearer $CERTYNEO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your.app/webhooks/certyneo",
    "events": ["envelope.completed", "recipient.signed"]
  }'

URLは公的にHTTPSでアクセス可能である必要があります。プライベートアドレスまたはlocalhostはサブスクリプション作成時のSSRF保護によって拒否されます。

サブスクリプションはキーと同じように環境ごとに分離されます。sk_test_ キーで作成された Webhook はテストエンベロープのイベントのみを受け取り、sk_live_ キーで作成された Webhook は実際のエンベロープのイベントのみを受け取ります。したがって、テストエンベロープが本番環境の URL に到達することはなく、各ペイロードは「sandbox」(true または false)を示します。

6つの実践

  • HMACの署名をチェックする 身体を読み取る前に タイムセーフの比較をします
  • `X-Certyneo-Delivery-Id`ヘッダーで重複排除してください(本体内の`id`と同じ)。すでに処理済みのイベントが2xxの喪失時に再配信される可能性があるため、識別子を基に処理済みかどうかをデータベースで確認し、保存してください。すべての試行で同じ識別子になります。
  • 最大10秒以内にHTTP 2xxで応答し、その後非同期で処理します(キュー)。これを超えると、配信は切断され、失敗としてカウントされます。
  • 粗質なボディログ + 完全なサインをデバッグする HMAC 検証はしばしばBOMや見えないホワイトスペースで失敗します
  • Webhooks→失敗ページを監視します:エンドポイントが無効化された場合はメールで通知されますが、イベントごとではありません—試行を使い果たしたイベントは自分で再生する必要があります。
  • ハンドラーで参照のみするのではなく、サブスクリプション時にイベントをフィルター処理し、アカウントごとに5つのサブスクリプション制限未満に保ちます。

これらのイベントを受け取るためにエンドポイントをホストする必要はありません:コネクタはサブスクリプションをあなたの代わりに設定し、エンベロープイベント上で直接フローを開始します。こちらを参照 Power AutomateおよびMicrosoft 365向けCertyneo.

更に進めるために

システム接続準備はいいか?

WebhooksおよびREST APIはStandardプラン以上に含まれています。アカウントを作成し、`sk_test_`キーを生成し、本番環境に移行する前にサンドボックスでエンドポイントを接続してください。