---
title: "契約書管理システムのAPI連携：Webhook受信から締結PDFの冪等な格納・基幹同期までの実装手順"
url: "https://www.issoh.co.jp/tech/details/18915/"
published: 2026-10-26
updated: 2026-10-26
categories: ["業務システム"]
publisher: "株式会社一創"
---

# 契約書管理システムのAPI連携：Webhook受信から締結PDFの冪等な格納・基幹同期までの実装手順

契約書管理システムのAPI連携は、電子契約サービスから「締結が終わった」という通知を受け取り、PDFと契約条件を取り出して契約書管理側と基幹・CRMへ配る処理です。この記事では、クラウドサインのWeb API（仕様書0.37系・2026年10月時点）を例に、Webhookの受信エンドポイント、アクセストークンの管理、締結PDFと合意締結証明書の取得、書類情報の契約マスタへの転記をcurlとPythonのコードで示します。あわせて、冪等キーによる二重登録の防止、取りこぼしを埋める差分照合、監査ログの項目、そして自前で作るべきかiPaaSで足りるかの判断まで扱います。

## まとめ：受信は軽く、真正性はAPI再取得で、格納は書類IDで冪等に

実装の骨格は4つです。Webhookは書類IDを受信テーブルに積んで即座に2xxを返すだけにする。格納はワーカーが書類IDでAPIから書類を取り直し、状態が締結済みのときだけPDF・証明書・書類情報を保存する。受信テーブルは書類IDを主キーにして、再送や重複通知が来ても1件にまとまるようにする。夜間に最終処理日時の差分で締結済み一覧を引き直し、Webhookの取りこぼしを埋める。

クラウドサインのWebhookには署名が付かず、400番台を返すと再送されません。この2点を知らずに作ると、偽の通知で処理が動く、一時障害で契約書が欠落する、という事故になります。基幹・CRMへの書き戻しや独自の監査要件があるなら自前実装、締結PDFをストレージに置くだけならiPaaSで十分、が判断の線です。

## 契約書管理システムのAPI連携で動かす範囲と電子契約・基幹・CRMの役割分担

最初に、どのシステムが何の正本を持つかを決めます。ここが曖昧だと、同期の向きと衝突時の勝ち負けが決まりません。

### 締結完了から契約マスタ更新までを4つの処理に分ける全体構成の描き方

連携は「受信」「取得」「格納」「配信」の4処理に分けて設計します。受信はWebhookを受けて書類IDを記録するだけ。取得は電子契約のAPIから書類データとPDFを引く処理で、ここで初めて外部APIを叩きます。格納は契約書管理側（ファイルストレージと契約マスタ）への書き込み。配信は基幹の取引先・債権管理やCRMの商談へ契約番号と期間を書き戻す処理です。

1. 受信：Webhookの本文から書類IDを取り出し、受信テーブルへ書く（外部APIは呼ばない）
2. 取得：書類IDで書類データを取り直し、状態が締結済みか確かめる
3. 格納：締結PDF・合意締結証明書・書類情報を保存し、契約マスタを更新する
4. 配信：契約マスタの変更を基幹・CRMへ反映する

4つを別のジョブにしておくと、CRM側が止まっていても格納までは進み、復旧後に配信だけをやり直せます。各データの正本をどこに置くかは、後述の同期の章で決める設計事項です。台帳にどの項目を持たせるかは[契約管理台帳の項目設計とシステム化の手順](https://www.issoh.co.jp/column/details/16946/)で整理しています。

### 契約書管理システム側APIで登録・更新・取得できる項目を仕様書で確かめる手順

格納先がSaaSの契約書管理システムなら、受け側のAPIが何を受け付けるかで設計が変わります。たとえばLegalOn Technologiesは2026年7月13日、契約書ファイルの登録、登録済み契約書の一覧と詳細の取得、取引先名や契約類型などの更新、削除ができるAPIを[公式発表](https://www.legalontech.com/jp/news/283)で公開しました。認証はOAuth 2.0のclient\_credentialsで、操作範囲はロールを持つワークスペース内に限られます。

受け側のAPI仕様書で見るのは、外部の書類IDを保存できる項目があるか、同じ契約を2回登録したら上書きか重複か、の2点です。外部キーの項目が無い製品では、自社側で「電子契約の書類ID ⇔ 契約書管理側のID」の対応表を持つことになります。製品選びの段階でこの点を比べたい場合は、[契約書管理システムの機能と選び方](https://www.issoh.co.jp/column/details/15208/)の比較軸に「API」を足してください。

### クラウドサインのプラン・APIオプション・環境別ホストを着手前に確認する項目

クラウドサインの[Webhook機能](https://help.cloudsign.jp/ja/articles/417935)はスタンダード・コーポレート・ビジネス・エンタープライズの各プランが対象で、通知先は最大20件まで登録できます。[Web APIの案内](https://help.cloudsign.jp/ja/articles/936884)によると、エンタープライズプランでは `/team_documents` と `/me` が使えません。さらに仕様書には、APIオプションの契約が無いユーザーには403の `webapi_option_required` を返すとあり、プラン名だけでなく契約内容の確認も要ります。

接続先は[Web API仕様書（OpenAPI 0.37.1）](https://api.swaggerhub.com/apis/CloudSign/cloudsign-web%5Fapi/0.37.1)の servers に4種類あります。本番 `api.cloudsign.jp`、サンドボックス `api-sandbox.cloudsign.jp`、それぞれのLGWAN版です。開発はサンドボックスのクライアントIDで進め、本番のクライアントIDは本番の秘密情報ストアにだけ置きます。電子契約サービスそのものを比べる段階なら、[電子契約システムの認証方式と連携の比較](https://www.issoh.co.jp/column/details/15320/)が先に読む記事です。

## クラウドサインのWebhookで締結完了を受け取る受信エンドポイントの実装

受信エンドポイントの仕事は、通知を記録して速く返すことだけです。PDFのダウンロードをここでやると応答が遅れます。

### 通知本文のdocumentIDとstatus値を仕様書の書類状態コードと照合する方法

ヘルプに載っているテスト送信の本文は、`documentID`・`status`・`userID`・`email`・`text` の5項目のJSONです。テスト送信では `status` が2になっています。ただしヘルプには status値の意味も、締結完了・取り消し・メール不達でどう本文が変わるかも書かれていません。

手がかりは仕様書の documentModel です。書類の status は、0＝下書き、1＝先方確認中、2＝締結済、3＝取消または却下、4＝テンプレート、13＝インポート書類と定義されています。通知の status も同じ体系と読めますが、確証はありません。通知の値は受信テーブルに参考値として残すだけにし、格納の可否はAPIから取り直した書類の status で決めます。

### 署名の無い通知を信用せず、受信URLの秘密化とAPI再取得で真正性を担保する設計

クラウドサインのヘルプには、Webhookの署名・認証方式・送信元IPの記載がありません。比較すると、Docusign Connectは本文をHMAC-SHA256でハッシュした値を鍵ごとに `X-Docusign-Signature-1` のようなヘッダで付け、鍵は最大100個まで持てると[Docusignの開発者ブログ](https://www.docusign.com/blog/developers/event-notifications-using-json-sim-and-hmac)が説明しています。署名がある製品なら、その検証を受信の最初に置きます（実装は[Webhookの署名検証と再送の実装](https://www.issoh.co.jp/tech/details/1378/)を参照）。

署名が無い場合の守り方は2段です。1段目は受信URLにランダムな秘密文字列を含め、一致しないリクエストは404で捨てる。2段目が本命で、通知本文の中身は書類IDしか使わず、PDFも状態も必ず自社のアクセストークンでAPIから取り直します。偽の通知が届いても、存在しない書類IDならAPIが404を返し、他社の書類IDなら403で止まります。通知は「調べに行くきっかけ」であって、事実の根拠ではありません。

### 400番台は再送されない仕様に合わせた応答コードの返し分けと受信コード

[Webhook実行時の挙動](https://help.cloudsign.jp/ja/articles/9977727)では、500番台を返したときだけ10分間隔で最大3回まで自動再送し、400番台は現行仕様で送信成功として扱って再送しないとされています。タイムアウトの秒数は書かれていません。DBが一時的に落ちているときに400を返すと、その通知は二度と来ない。一時障害は503、本文が壊れていて再送されても直らないものは400、と返し分けます。

```
import os
import hmac
from flask import Flask, request, abort
import psycopg

app = Flask(__name__)
PATH_SECRET = os.environ["CLOUDSIGN_WEBHOOK_PATH_SECRET"]

UPSERT = """
INSERT INTO cloudsign_inbox (document_id, received_status)
VALUES (%s, %s)
ON CONFLICT (document_id) DO UPDATE
   SET received_status = EXCLUDED.received_status,
       received_at = now(),
       state = CASE WHEN cloudsign_inbox.state = 'stored'
                    THEN 'stored' ELSE 'pending' END
"""

@app.post("/hooks/cloudsign/<secret>")
def receive(secret):
    if not hmac.compare_digest(secret, PATH_SECRET):
        abort(404)
    body = request.get_json(silent=True) or {}
    doc_id = body.get("documentID")
    if not doc_id:
        return "", 400   # 再送されても直らないので400（再送されない）
    try:
        with psycopg.connect(os.environ["DATABASE_URL"]) as conn:
            conn.execute(UPSERT, (doc_id, body.get("status")))
    except psycopg.Error:
        return "", 503   # 500番台なら10分間隔で最大3回再送される
    return "", 202
```

書き込みは1行のupsertだけで、格納済み（stored）の書類に再び通知が来ても状態は戻しません。秘密文字列の比較に `hmac.compare_digest` を使うのは、文字列比較の所要時間から一致した文字数を推測されないためです。

## 締結済みPDFと合意締結証明書をWeb APIで取得して格納するPythonコード

ここからは受信テーブルを読むワーカー側です。外部APIを呼ぶ処理はすべてこちらに寄せます。

### 有効期限1時間のアクセストークンをキャッシュして401と429に備える実装

仕様書によると、アクセストークンは `POST /token` に `client_id` をフォーム形式で送って取得し、有効期限は発行から3,600秒です。期限内にもう一度要求しても新しいトークンは発行されず、応答の `expires_in` は「残り秒数」と定義されています。つまり毎回取りに行っても延命にはならないので、残り秒数から失効時刻を計算してキャッシュする設計です。まずcurlで流れを確かめます。

```
# 1) トークン取得（サンドボックス）
curl -s -X POST https://api-sandbox.cloudsign.jp/token \
  -d "client_id=${CLOUDSIGN_CLIENT_ID}"
# => {"access_token":"...","expires_in":3600,"token_type":"Bearer"}

# 2) 書類データの取得
curl -s -H "Authorization: Bearer ${TOKEN}" \
  https://api-sandbox.cloudsign.jp/documents/${DOC_ID}

# 3) 締結PDFと合意締結証明書のダウンロード
curl -s -H "Authorization: Bearer ${TOKEN}" -o signed.pdf \
  https://api-sandbox.cloudsign.jp/documents/${DOC_ID}/files/${FILE_ID}
curl -s -H "Authorization: Bearer ${TOKEN}" -o certificate.pdf \
  https://api-sandbox.cloudsign.jp/documents/${DOC_ID}/certificate
```

レート制限も仕様書に数値があります。同一トークンでのリクエストが1分間に800回を超えると以降はブロックされ、1分の停止期間を挟んで自動で解除されます。エラー値は `too_many_requests` です。ワーカーでは、429なら60秒待つ、401ならキャッシュを捨てて取り直す、500番台は指数的に間隔を延ばす、の3分岐を入れます。

```
import os
import time
import requests

BASE = os.environ.get("CLOUDSIGN_BASE", "https://api.cloudsign.jp")
_cache = {"token": None, "exp": 0.0}

def access_token():
    if _cache["token"] and time.time() < _cache["exp"] - 60:
        return _cache["token"]
    r = requests.post(f"{BASE}/token",
                      data={"client_id": os.environ["CLOUDSIGN_CLIENT_ID"]},
                      timeout=10)
    r.raise_for_status()
    j = r.json()
    _cache.update(token=j["access_token"], exp=time.time() + j["expires_in"])
    return j["access_token"]

def api_get(path, **kw):
    for attempt in range(5):
        r = requests.get(f"{BASE}{path}",
                         headers={"Authorization": f"Bearer {access_token()}"},
                         timeout=60, **kw)
        if r.status_code == 401:
            _cache["token"] = None          # 失効したトークンを捨てて取り直す
            continue
        if r.status_code == 429:
            time.sleep(60)                  # 1分の停止期間後に自動解除
            continue
        if r.status_code >= 500:
            time.sleep(2 ** attempt)
            continue
        r.raise_for_status()                # 403・404はここで例外にする
        return r
    raise RuntimeError(f"retry exhausted: {path}")
```

失効の60秒前に取り直すのは、取得直後に期限切れになる境界のずれを避けるためです。ワーカーを複数プロセスで動かすなら、キャッシュはRedisなどに置いて1つのトークンを共有し、800回の上限は全プロセスの合計で数えます。

### 書類データからファイルIDを取り出し、締結PDFと証明書を保存する処理

書類データ（`GET /documents/{documentID}`）の `files` には、ファイルごとに `id`・`name`・`order`（アップロード順）・`total_pages` が入っています。1つの書類に複数のPDFが束ねられることがあるので、ファイル数を1と決め打ちしません。合意締結証明書は `/certificate` から別に取ります。

```
def store_document(doc_id, save_blob):
    doc = api_get(f"/documents/{doc_id}").json()
    if doc["status"] != 2:                  # 2＝締結済。それ以外は格納しない
        return {"state": "skipped", "status": doc["status"]}
    keys = []
    for f in sorted(doc["files"], key=lambda x: x["order"]):
        pdf = api_get(f"/documents/{doc_id}/files/{f['id']}").content
        key = f"contracts/{doc_id}/{f['order']:02d}_{f['id']}.pdf"
        save_blob(key, pdf, content_type="application/pdf")
        keys.append(key)
    cert = api_get(f"/documents/{doc_id}/certificate").content
    save_blob(f"contracts/{doc_id}/certificate.pdf", cert,
              content_type="application/pdf")
    attr = api_get(f"/documents/{doc_id}/attribute").json()
    return {"state": "stored", "title": doc["title"],
            "files": keys, "attribute": attr}
```

保存キーにファイル名でなくファイルIDを使うのは、利用者が付けたファイル名に記号や重複が混ざるためです。表示用のファイル名は契約マスタの別列に持ちます。同じキーへの上書き保存になるので、この関数は何度呼んでも結果が変わりません。

### 書類情報の契約期間・自動更新・解約通知期限を契約マスタへ写す対応表

`GET /documents/{documentID}/attribute` が返す書類情報は、クラウドサイン上で入力された管理用の項目です。仕様書では日付と金額がnullを許容しており、入力されていない書類も普通にあります。

| 書類情報の項目                   | 契約マスタの列   | 変換と注意           |
| ------------------------- | --------- | --------------- |
| counterparty              | 取引先名（表示用） | 基幹の取引先コードとは別に持つ |
| contract\_at              | 締結日       | nullなら最終処理日時で補う |
| validity\_start\_at       | 契約開始日     | YYYY-MM-DDの日付型  |
| validity\_end\_at         | 契約終了日     | null許容・期日通知の起点  |
| validity\_end\_notice\_at | 解約通知期限    | 更新漏れ通知に使う       |
| auto\_update              | 自動更新      | 0指定なし・1あり・2なし   |
| local\_id                 | 管理番号      | 基幹の契約番号との照合キー   |
| amount                    | 取引金額      | 整数・null許容       |

実務でまず埋めたいのは解約通知期限と自動更新の2列です。更新漏れを防ぐ通知の条件として使えるのは、この2つの項目です。書類情報が空のまま締結される運用だと連携しても期日管理は始まらないので、送信テンプレートで入力を必須にするか、格納後に法務担当が補う画面を用意します。電子帳簿保存法の電子取引データとして保存するなら、取引年月日・取引金額・取引先で検索できる列を契約マスタに持たせます。

## 契約マスタと基幹・CRMの同期を差分取得・冪等キー・リトライで組む設計

Webhookは「3回再送したら終わり」の仕組みです。取りこぼしを前提に、受信テーブルと夜間照合の2本立てで欠落をゼロに寄せます。

### 書類IDを冪等キーにして二重登録を防ぐ受信テーブルのDDLと更新規則

電子契約の書類IDは、仕様書の定義では32〜36文字の文字列です。これを受信テーブルの主キーにすると、再送・重複通知・夜間照合が同じ書類を何度投入しても1行に収まります。冪等キーの考え方そのものは[冪等キーの発行・保存・再生の実装](https://www.issoh.co.jp/tech/details/16293/)で詳しく扱っています。

```
CREATE TABLE cloudsign_inbox (
  document_id      varchar(36) PRIMARY KEY,
  received_status  integer,                  -- 通知本文のstatus（参考値）
  state            varchar(16) NOT NULL DEFAULT 'pending',
                   -- pending / stored / skipped / failed
  attempts         integer NOT NULL DEFAULT 0,
  last_error       text,
  received_at      timestamptz NOT NULL DEFAULT now(),
  stored_at        timestamptz
);
CREATE INDEX cloudsign_inbox_pending
  ON cloudsign_inbox (received_at) WHERE state IN ('pending', 'failed');

-- ワーカーが1件ずつ取り出す（複数ワーカーでも同じ行を掴まない）
SELECT document_id FROM cloudsign_inbox
 WHERE state IN ('pending', 'failed') AND attempts < 5
 ORDER BY received_at
 LIMIT 1
 FOR UPDATE SKIP LOCKED;
```

更新規則は単純にします。格納に成功したら stored と stored\_at、締結済み以外なら skipped、例外なら attempts を1増やして failed と last\_error。attempts が5に達した行はワーカーが拾わなくなるので、後述のアラートで人が見ます。`FOR UPDATE SKIP LOCKED` はPostgreSQLの構文で、ワーカーを増やしても同じ書類を2プロセスで処理しません。

### Webhookの取りこぼしを最終処理日時の差分取得で埋める夜間照合ジョブ

書類一覧（`GET /documents`）は `status` で状態を、`from` と `to` で書類の最終処理日時（RFC3339）を絞り込めます。1ページ最大100件で、`page` が大きすぎる場合の documents の応答は空配列です。締結済みの書類について最終処理日時は最後の同意日時と等しいと定義されているので、「前回照合の開始時刻から今回まで」に締結された書類を漏れなく引けます。

```
def reconcile(since_iso, until_iso, enqueue):
    page = 1
    while True:
        j = api_get("/documents", params={
            "status": 2, "from": since_iso, "to": until_iso, "page": page,
            "with_files": "n", "with_participants": "n",
        }).json()
        docs = j.get("documents", [])
        if not docs:
            break
        for d in docs:
            enqueue(d["id"])        # 受信テーブルへupsert（既存なら何もしない）
        page += 1
```

照合の窓は前回の開始時刻から少し重ねて取ります。重なりで同じ書類が再投入されても、受信テーブルの主キーで吸収されるからです。注意点が1つあります。メンバー全員がやり取りした書類を一度に引くのは別の `/team_documents` で、仕様書では管理者権限の無いアカウントと複数部署管理機能を使うアカウントは利用できないとされ、エンタープライズプランでも使えません。この条件に当たる場合は、送信を担う利用者ごとのクライアントIDで照合を回す設計になります。

### 基幹・CRMへの書き戻しで二重計上を起こさない外部キーとupsertの規則

配信先でも同じ書類を重複処理しないために、冪等性が必要です。基幹の契約テーブルやCRMの商談に「電子契約の書類ID」を外部キーとして持たせ、その値でupsertします。CRM側がupsertを持つ例として、HubSpotは任意のユニークプロパティを指定して作成と更新を1回で済ませるAPIを持っており、実装は[HubSpot APIのupsertとWebhookの実装](https://www.issoh.co.jp/tech/details/18636/)で解説しています。

競合時の勝ち負けも決めておきます。契約期間と金額は契約マスタが正本で、CRMで書き換えても次の配信で上書きされる。取引先コードは基幹が正本で、契約マスタ側では読み取り専用にする。締結済みPDFの正本は契約書管理側です。こう決めないと、双方向同期が値を往復させてループします。REST連携の認証とエラー処理の一般形は[API連携の実装方法（REST・認証・エラー処理）](https://www.issoh.co.jp/tech/details/15351/)にまとめています。

## 監査ログとエラー監視で契約データの欠落と不正取得を検知する運用設定

契約書は「いつ誰が取り出したか」を後から問われる文書です。連携ジョブが取得した記録も、人が閲覧した記録と同じ粒度で残します。

### 誰がいつどの契約書を取得したかを追える監査ログの項目とテーブル定義

監査ログは追記のみのテーブルにし、更新・削除の権限をアプリのDBユーザーから外します。actor にはジョブ名を入れ、人の操作と区別できるようにします。

```
CREATE TABLE contract_audit_log (
  id           bigserial PRIMARY KEY,
  document_id  varchar(36) NOT NULL,
  action       varchar(32) NOT NULL,  -- fetch_pdf / fetch_certificate / upsert_master / push_crm
  actor        varchar(64) NOT NULL,  -- 例: job:cloudsign-worker、user:1234
  result       varchar(16) NOT NULL,  -- ok / skipped / error
  http_status  integer,
  detail       text,                  -- エラー値（too_many_requests 等）。PDF本文や個人情報は入れない
  occurred_at  timestamptz NOT NULL DEFAULT now()
);
REVOKE UPDATE, DELETE ON contract_audit_log FROM app_user;
```

detail に通知本文をそのまま入れないのは、本文に送信者のメールアドレスが含まれるためです。必要なのはエラー値とHTTPステータスで、個人情報はログに複製しません。

### 格納失敗を放置しないアラート条件と、受信テーブルからの再処理手順

アラートは件数より「滞留時間」で掛けます。pending のまま30分を超えた行が1件でもある、attempts が上限に達した failed がある、夜間照合で新規に見つかった書類が1件以上ある、の3条件です。3つ目はWebhookが取りこぼしたことを意味するので、件数が増えるなら受信側の応答コードかURLの設定を疑います。

再処理は受信テーブルの state を pending に戻し、attempts を0にするだけです。格納が上書き保存、配信がupsertなので、何度流しても結果は変わりません。403（権限なし・`webapi_option_required`）はリトライで直らないため、ワーカーは即 failed にして人に回します。

## API連携を自前で作る条件と、iPaaSやノーコード連携で足りる場面の判断

ここまでのコードは数百行で収まりますが、運用まで含めると保守対象が1つ増えます。作るかどうかは、締結後に何をしたいかで決まります。

### 自前実装を選ぶ条件：基幹への書き戻しと独自の監査要件があるケース

次のどれかに当てはまるなら自前実装を選びます。契約番号や期間を基幹・CRMへ書き戻し、その値で請求や更新通知を動かしている。監査ログを社内の保存期間と項目で持つ必要がある。送信者ごとのクライアントIDを束ねて照合する必要がある（`/team_documents` が使えないプランや部署構成の場合）。どれも、ノーコード連携の画面設定では受信テーブル・冪等キー・夜間照合を組みにくい要件です。

一創では、電子契約と契約書管理・基幹・CRMをつなぐ受信基盤、同期ジョブ、監査ログの設計から実装・保守までを[API開発・システム連携](https://www.issoh.co.jp/service/system/api/)として受託しています。既存の契約書管理システムから別製品へ移る途中で連携を作り直す場合は、[契約書管理システムの乗り換え手順](https://www.issoh.co.jp/column/details/16962/)の移行データ設計と合わせて進めると手戻りが減ります。

### 自前実装を見送る場面：月数十件の締結PDFを保存するだけの連携

締結件数が月に数十件で、やりたいことが「締結済みPDFを共有ドライブのフォルダに置く」「チャットに通知する」だけなら、自前実装は過剰です。国産iPaaSのYoomのように、クラウドサインの締結を起点に保存や通知を組めるサービスを使えば、受信サーバーとDBを持たずに済みます。

ただし見送ってよいのは、PDFが1件欠けても人が気付ける規模に限ります。Webhookが届かなかったことは誰も知らせてくれないため、月1回、締結済み一覧と保存先のファイル数を突き合わせてください。この突き合わせが追いつかなくなったら、自前の夜間照合へ移る合図です。

## よくある質問

契約書管理システムのAPI連携を設計するときに、実装者と発注側の双方から出やすい質問をまとめました。

### 契約書管理システムとのAPI連携にはクラウドサインのどのプランが必要ですか？

Webhookはスタンダード・コーポレート・ビジネス・エンタープライズの各プランが対象です。Web APIも同じプランが対象ですが、仕様書にはAPIオプションの契約が無いユーザーへ403の webapi\_option\_required を返すとあり、契約内容の確認が要ります。エンタープライズプランではチーム全員の書類一覧（team\_documents）と利用者情報（me）が使えないため、差分照合の設計が変わります。

### クラウドサインのWebhookに署名検証の仕組みはありますか？

2026年10月時点のヘルプには、Webhookの署名・認証方式・送信元IPの記載がありません。受信URLに秘密文字列を含め、通知からは書類IDだけを使い、状態とPDFは自社のアクセストークンでAPIから取り直してください。偽の通知が来ても、自社が閲覧できない書類は403で止まります。

### Webhookが届かなかった締結済み書類はどうやって拾えばよいですか？

書類一覧のAPIを status=2（締結済）と最終処理日時の from・to で絞り、前回の照合時刻以降の書類を引き直します。見つかった書類IDを受信テーブルへupsertすれば、格納済みのものは主キーで吸収され、未格納のものだけがワーカーに拾われる仕組みです。

### 締結済みPDFと合意締結証明書は両方とも保存すべきですか？

両方保存します。締結済みPDFは契約の中身、合意締結証明書は誰がいつ同意したかの記録で、取得するAPIも files と certificate に分かれています。締結の経緯を示す場面で要るのは証明書の側です。電子契約サービスを解約すると取り直せなくなるため、締結時点で書類IDの配下にまとめて保存してください。

### iPaaSと自前実装のどちらから始めるべきですか？

締結PDFの保存と通知だけならiPaaSから始め、基幹・CRMへの書き戻しや自社項目の監査ログが要るなら最初から自前実装にします。iPaaSで始める場合も、書類IDを保存先のファイル名か列に残してください。後から自前の受信テーブルへ移すとき、その値が突き合わせのキーになるからです。

## 関連記事

- [契約書管理システムとは？機能・選び方と比較のポイント・導入判断を解説](https://www.issoh.co.jp/column/details/15208/)：API連携の前に、製品の機能と選定基準を整理した親記事
- [電子契約システムの比較と選び方とは？認証方式・連携・費用の判断軸を解説](https://www.issoh.co.jp/column/details/15320/)：通知を送る側となる電子契約サービスの比較
- [契約管理台帳のシステム化｜項目設計とExcelが限界になる条件・移行の手順](https://www.issoh.co.jp/column/details/16946/)：契約マスタに持たせる項目の設計
- [Webhookの作り方と実装方法｜受信側の署名検証・冪等性と送信側の再送をNode.js・Pythonで解説](https://www.issoh.co.jp/tech/details/1378/)：署名がある製品で受信を作るときの実装
- [冪等キーとは？Idempotency-Keyの発行・保存・再生の実装と二重課金の止め方を解説【2026年版】](https://www.issoh.co.jp/tech/details/16293/)：書類IDを冪等キーにする考え方の土台

---

出典: [契約書管理システムのAPI連携：Webhook受信から締結PDFの冪等な格納・基幹同期までの実装手順](<https://www.issoh.co.jp/tech/details/18915/>)（株式会社一創）
