Contents
GMOサインとは – 提供機能と活用シーン {#gmoサインとは-提供機能と活用シーン}
GMOサインは 「書類アップロード → 署名依頼作成 → ステータス取得」 の3ステップで電子契約を自動化できるサービスです。RESTful API が中心となっているため、既存の業務システムに最小限のコード追加で統合可能です。
主な提供機能 {#主な提供機能}
-
書類アップロード
PDF・画像(PNG/JPG)を Base64 エンコードして送信し、document_idを取得します。 -
署名依頼作成
アップロード済みドキュメントに対し、署名者情報と有効期限expire_at(ISO8601 形式)を設定し、署名リンク (sign_url) を生成します。 -
ステータス管理
取得 API とキャンセル API により、リアルタイムで進捗確認・途中停止が可能です。
活用シーン別事例 {#活用シーン別事例}
| シナリオ | ビジネス課題 | GMOサインの活かし方 |
|---|---|---|
| 営業案件の見積書送付 | 手動メール添付でミスが多い | 見積 PDF を API でアップロード → 署名リンクを自動生成 → メール本文に埋め込み |
| 社内稟議・承認フロー | 社内共有ドライブのバージョン管理が煩雑 | 稟議書をサインフロー化し、ステータス取得で進捗可視化 |
| 請求書への顧客署名 | 請求書送付後に支払確認まで時間がかかる | 請求書 PDF に署名依頼 → 完了通知を受け取り、会計システムへ自動連携 |
API 利用開始までの事前準備 {#api-利用開始までの事前準備}
本章では、実装に入る前に必要なアカウント作成・キー取得・環境設定手順を解説します。「開発者が迷わず次のステップへ進める」 ことを意識しています。
手順概要 {#手順概要}
- 会員登録 – 法人アカウント作成
- API キー取得 – テスト用・本番用キーを発行
- 環境切替 – テスト環境 URL と本番環境 URL を明確に分離
詳細手順とポイント {#詳細手順とポイント}
| ステップ | 操作内容 | 注意点 |
|---|---|---|
| 1️⃣ 会員登録 | GMOサイン公式サイトで法人アカウントを作成(管理者メールが必須) | 管理者権限のメールは社内で共有しないこと |
| 2️⃣ API キー取得 | 管理画面 「API 設定」→「新規キー発行」 → X-API-KEY と Secret Key が発行される |
キーはテスト・本番別に管理し、環境変数で保持 |
| 3️⃣ 環境切替 | テスト環境 URL: https://api-test.gmosign.com 本番環境 URL: https://api.gmosign.com |
キーとエンドポイントは必ずセットで管理し、混在しないよう CI でチェック |
| 4️⃣ スコープ設定 | 「書類アップロード」・「署名依頼作成」のスコープを有効化 | 未設定だと 403 Forbidden が返るので必ず確認 |
ポイント:テスト環境は無料枠(月間 1,000 件)がありますが、本番に切り替える前に必ずキー・URL のペアが正しいか CI パイプラインで検証してください。
認証方式とリクエストヘッダー構成 {#認証方式とリクエストヘッダー構成}
GMOサインは HMAC SHA256 による署名認証を採用しています。以下では、署名生成ロジックと実際の HTTP ヘッダー例をコード付きで示します。
署名生成手順 {#署名生成手順}
- パラメータ準備
nonce:ランダム文字列(12 桁の十六進数)timestamp:Unix エポック秒(例:1729875600)-
HTTP メソッド、リクエストパス、リクエストボディ(JSON 文字列、GET の場合は空文字)
-
ベース文字列作成
|
1 2 3 4 5 6 |
baseString = nonce + "\n" + timestamp + "\n" + httpMethod + "\n" + requestPath + "\n" + requestBody |
- HMAC SHA256 計算
signature = HMAC_SHA256(secretKey, baseString)(16 進数表記)
HTTP ヘッダー例(Python) {#httpヘッダー例python}
|
1 2 3 4 5 6 7 |
POST https://api-test.gmosign.com/v1/documents HTTP/1.1 Content-Type: application/json X-API-KEY: YOUR_API_KEY X-NONCE: a3f9c7d2e1b6 X-TIMESTAMP: 1729875600 X-SIGNATURE: <calculated signature> |
重要:
nonceとtimestampはリクエストごとに必ず更新し、同一組み合わせの再利用はリプレイ攻撃につながります。
主要エンドポイントとサンプル JSON {#主要エンドポイントとサンプル-json}
本章では、実務で頻繁に使用する 3 つ のエンドポイントを中心に、リクエスト/レスポンス例と必須パラメータを示します。各エンドポイントはテスト環境でも同一仕様です。
書類アップロード {#書類アップロード}
アップロードは Base64 エンコードされたバイナリデータを送信し、
document_idを取得します。
| 項目 | 説明 |
|---|---|
| HTTP メソッド / パス | POST /v1/documents |
| 必須パラメータ | file_name, content_type, file_base64 |
リクエスト例
|
1 2 3 4 5 6 |
{ "file_name": "contract_2024.pdf", "content_type": "application/pdf", "file_base64": "JVBERi0xLjQKJc..." } |
レスポンス例
|
1 2 3 4 5 6 |
{ "document_id": "doc_5f7e8a9b3c1d2", "status": "uploaded", "upload_at": "2024-10-25T08:15:30Z" } |
署名依頼作成 {#署名依頼作成}
expire_atは ISO8601 UTC(例:2024-11-01T23:59:59Z)で指定します。整数の Unix タイムスタンプから変換する方法は Python サンプルで解説しています。
| 項目 | 説明 |
|---|---|
| HTTP メソッド / パス | POST /v1/signings |
| 必須パラメータ | document_id, signers, expire_at |
リクエスト例
|
1 2 3 4 5 6 7 8 9 10 11 12 |
{ "document_id": "doc_5f7e8a9b3c1d2", "signers": [ { "name": "山田 太郎", "email": "taro.yamada@example.com", "order": 1 } ], "expire_at": "2024-11-01T23:59:59Z" } |
レスポンス例
|
1 2 3 4 5 6 |
{ "signing_id": "sgn_a9c3d5e7f0b2", "status": "pending", "sign_url": "https://sign.gmosign.com/s/sgn_a9c3d5e7f0b2" } |
ステータス取得・キャンセル {#ステータス取得キャンセル}
| 操作 | HTTP メソッド / パス |
|---|---|
| ステータス取得 | GET /v1/signings/{signing_id} |
| キャンセル | POST /v1/signings/{signing_id}/cancel |
ステータス取得レスポンス例
|
1 2 3 4 5 6 |
{ "signing_id": "sgn_a9c3d5e7f0b2", "status": "completed", "signed_at": "2024-10-27T14:20:00Z" } |
キャンセル成功レスポンス例
|
1 2 3 4 5 6 |
{ "signing_id": "sgn_a9c3d5e7f0b2", "status": "cancelled", "cancel_at": "2024-10-28T09:05:12Z" } |
公式サンプルコード実装例 (Python・PHP・Node.js) {#公式サンプルコード実装例pythonphpnodejs}
以下のスニペットは 「書類アップロード → 署名依頼作成 → ステータス取得」 のフローをシンプルに示しています。共通ロジック(make_signature、環境変数取得)は各言語で同一仕様です。
コード中のコメントはすべて英語に統一し、変数名も snake_case(Python) / camelCase(Node.js)/ PascalCase(PHP)で統一しました。
共通ポイント
| 項目 | 内容 |
|---|---|
| 環境変数 | API_KEY, SECRET_KEY を使用し、ハードコーディングを回避 |
| nonce 生成 | 12 桁の十六進数(例: a3f9c7d2e1b6) |
| timestamp | Unix エポック秒 (int(time.time())) |
| expire_at | Python では datetime.utcfromtimestamp(ts).isoformat() + "Z" に変換 |
Python(requests)サンプル {#pythonサンプル}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
import os, json, time, hashlib, hmac, base64, random, datetime import requests API_KEY = os.getenv('API_KEY') SECRET_KEY = os.getenv('SECRET_KEY').encode() BASE_URL = 'https://api-test.gmosign.com/v1' def make_signature(nonce: str, ts: int, method: str, path: str, body: str) -> str: """ Generate HMAC SHA256 signature required by GMO Sign. Returns a lower‑case hex string. """ base = f"{nonce}\n{ts}\n{method}\n{path}\n{body}" return hmac.new(SECRET_KEY, base.encode(), hashlib.sha256).hexdigest() def upload_document() -> str: """Upload a PDF and return document_id.""" with open('contract_2024.pdf', 'rb') as f: b64 = base64.b64encode(f.read()).decode() payload = { "file_name": "contract_2024.pdf", "content_type": "application/pdf", "file_base64": b64 } body = json.dumps(payload) nonce = ''.join(random.choices('0123456789abcdef', k=12)) ts = int(time.time()) path = '/documents' sig = make_signature(nonce, ts, 'POST', path, body) headers = { "Content-Type": "application/json", "X-API-KEY": API_KEY, "X-NONCE": nonce, "X-TIMESTAMP": str(ts), "X-SIGNATURE": sig } resp = requests.post(BASE_URL + path, data=body, headers=headers) resp.raise_for_status() return resp.json()['document_id'] def create_signing(document_id: str) -> str: """Create a signing request with 7‑day expiry.""" expire_ts = int(time.time()) + 7 * 24 * 3600 # Convert integer timestamp to ISO8601 UTC string (e.g. 2024-11-01T23:59:59Z) expire_at = datetime.datetime.utcfromtimestamp(expire_ts).strftime('%Y-%m-%dT%H:%M:%SZ') payload = { "document_id": document_id, "signers": [ {"name": "山田 太郎", "email": "taro.yamada@example.com", "order": 1} ], "expire_at": expire_at } body = json.dumps(payload) nonce = ''.join(random.choices('0123456789abcdef', k=12)) ts = int(time.time()) path = '/signings' sig = make_signature(nonce, ts, 'POST', path, body) headers = { "Content-Type": "application/json", "X-API-KEY": API_KEY, "X-NONCE": nonce, "X-TIMESTAMP": str(ts), "X-SIGNATURE": sig } resp = requests.post(BASE_URL + path, data=body, headers=headers) resp.raise_for_status() return resp.json()['signing_id'] def get_status(signing_id: str) -> str: """Poll the signing status.""" nonce = ''.join(random.choices('0123456789abcdef', k=12)) ts = int(time.time()) path = f"/signings/{signing_id}" sig = make_signature(nonce, ts, 'GET', path, '') headers = { "X-API-KEY": API_KEY, "X-NONCE": nonce, "X-TIMESTAMP": str(ts), "X-SIGNATURE": sig } resp = requests.get(BASE_URL + path, headers=headers) resp.raise_for_status() return resp.json()['status'] if __name__ == '__main__': doc_id = upload_document() print('Document ID:', doc_id) signing_id = create_signing(doc_id) print('Signing ID:', signing_id) time.sleep(5) # simple polling example status = get_status(signing_id) print('Current Status:', status) |
PHP(Guzzle)サンプル {#phpサンプル}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
<?php /** * GMO Sign API sample (upload → create signing → get status) * Requires: composer require guzzlehttp/guzzle */ require 'vendor/autoload.php'; use GuzzleHttp\Client; $apiKey = getenv('API_KEY'); $secretKey = getenv('SECRET_KEY'); $baseUrl = 'https://api-test.gmosign.com/v1'; function makeSignature(string $nonce, int $ts, string $method, string $path, string $body, string $secret): string { $base = implode("\n", [$nonce, $ts, $method, $path, $body]); return hash_hmac('sha256', $base, $secret); } // ---------- 1. Upload Document ---------- $client = new Client(['base_uri' => $baseUrl]); $fileData = base64_encode(file_get_contents(__DIR__.'/contract_2024.pdf')); $nonce = bin2hex(random_bytes(6)); $ts = time(); $path = '/documents'; $requestBody = json_encode([ 'file_name' => 'contract_2024.pdf', 'content_type'=> 'application/pdf', 'file_base64' => $fileData ]); $signature = makeSignature($nonce, $ts, 'POST', $path, $requestBody, $secretKey); $response = $client->post($path, [ 'headers' => [ 'Content-Type' => 'application/json', 'X-API-KEY' => $apiKey, 'X-NONCE' => $nonce, 'X-TIMESTAMP' => $ts, 'X-SIGNATURE' => $signature ], 'body' => $requestBody ]); $docId = json_decode($response->getBody(), true)['document_id']; // ---------- 2. Create Signing ---------- $nonce = bin2hex(random_bytes(6)); $ts = time(); $path = '/signings'; $expireAt = (new DateTime('now', new DateTimeZone('UTC'))) ->modify('+7 days') ->format('Y-m-d\TH:i:s\Z'); $requestBody = json_encode([ 'document_id' => $docId, 'signers' => [ ['name' => '山田 太郎', 'email' => 'taro.yamada@example.com', 'order' => 1] ], 'expire_at' => $expireAt ]); $signature = makeSignature($nonce, $ts, 'POST', $path, $requestBody, $secretKey); $response = $client->post($path, [ 'headers' => [ 'Content-Type' => 'application/json', 'X-API-KEY' => $apiKey, 'X-NONCE' => $nonce, 'X-TIMESTAMP' => $ts, 'X-SIGNATURE' => $signature ], 'body' => $requestBody ]); $signingId = json_decode($response->getBody(), true)['signing_id']; // ---------- 3. Get Status ---------- sleep(5); $nonce = bin2hex(random_bytes(6)); $ts = time(); $path = "/signings/{$signingId}"; $signature = makeSignature($nonce, $ts, 'GET', $path, '', $secretKey); $response = $client->get($path, [ 'headers' => [ 'X-API-KEY' => $apiKey, 'X-NONCE' => $nonce, 'X-TIMESTAMP' => $ts, 'X-SIGNATURE' => $signature ] ]); $status = json_decode($response->getBody(), true)['status']; echo "Signing status: {$status}\n"; ?> |
Node.js(Axios)サンプル {#nodejsサンプル}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
// npm install axios crypto dotenv require('dotenv').config(); const axios = require('axios'); const crypto = require('crypto'); const fs = require('fs'); const API_KEY = process.env.API_KEY; const SECRET_KEY = process.env.SECRET_KEY; const BASE_URL = 'https://api-test.gmosign.com/v1'; function makeSignature(nonce, ts, method, path, body) { const base = `${nonce}\n${ts}\n${method}\n${path}\n${body}`; return crypto.createHmac('sha256', SECRET_KEY).update(base).digest('hex'); } // ---------- 1. Upload Document ---------- async function uploadDocument() { const fileB64 = fs.readFileSync('./contract_2024.pdf').toString('base64'); const payload = { file_name: 'contract_2024.pdf', content_type:'application/pdf', file_base64: fileB64 }; const body = JSON.stringify(payload); const nonce = crypto.randomBytes(6).toString('hex'); const ts = Math.floor(Date.now() / 1000); const path = '/documents'; const sig = makeSignature(nonce, ts, 'POST', path, body); const res = await axios.post(`${BASE_URL}${path}`, payload, { headers: { 'Content-Type': 'application/json', 'X-API-KEY' : API_KEY, 'X-NONCE' : nonce, 'X-TIMESTAMP' : ts, 'X-SIGNATURE' : sig } }); return res.data.document_id; } // ---------- 2. Create Signing ---------- async function createSigning(documentId) { const expireAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(); // ISO8601 UTC const payload = { document_id: documentId, signers: [{ name: '山田 太郎', email: 'taro.yamada@example.com', order: 1 }], expire_at: expireAt }; const body = JSON.stringify(payload); const nonce = crypto.randomBytes(6).toString('hex'); const ts = Math.floor(Date.now() / 1000); const path = '/signings'; const sig = makeSignature(nonce, ts, 'POST', path, body); const res = await axios.post(`${BASE_URL}${path}`, payload, { headers: { 'Content-Type': 'application/json', 'X-API-KEY' : API_KEY, 'X-NONCE' : nonce, 'X-TIMESTAMP' : ts, 'X-SIGNATURE' : sig } }); return res.data.signing_id; } // ---------- 3. Get Status ---------- async function getStatus(signingId) { const nonce = crypto.randomBytes(6).toString('hex'); const ts = Math.floor(Date.now() / 1000); const path = `/signings/${signingId}`; const sig = makeSignature(nonce, ts, 'GET', path, ''); const res = await axios.get(`${BASE_URL}${path}`, { headers: { 'X-API-KEY' : API_KEY, 'X-NONCE' : nonce, 'X-TIMESTAMP' : ts, 'X-SIGNATURE' : sig } }); return res.data.status; } // ---------- Execution ---------- (async () => { const docId = await uploadDocument(); console.log('Document ID:', docId); const signingId = await createSigning(docId); console.log('Signing ID:', signingId); // simple polling after a short wait await new Promise(r => setTimeout(r, 5000)); const status = await getStatus(signingId); console.log('Current Status:', status); })(); |
運用・トラブルシューティングガイド {#運用トラブルシューティングガイド}
本章では、実装後に頻出するエラーと対処法、テスト→本番移行時のチェックリスト、セキュリティベストプラクティス、レートリミット・課金概要、FAQ をまとめます。
エラーコード一覧と推奨対応 {#エラーコード一覧}
| HTTP ステータス | 主な原因 | 具体的対処 |
|---|---|---|
| 400 Bad Request | パラメータ欠落・JSON 構文エラー | 必須項目を公式サンプルと比較し、file_base64 が正しくエンコードされているか再確認 |
| 401 Unauthorized | X-API-KEY 無効、署名不一致 |
キーが最新か確認。nonce・timestamp の生成ロジックをテスト環境でデバッグ |
| 403 Forbidden | スコープ未付与、IP 制限 | 管理画面で documents, signings スコープを有効化。必要なら IP ホワイトリストに開発サーバを追加 |
| 404 Not Found | リクエストパスミス、ID が存在しない | パスと ID をログ出力し、テスト環境/本番環境が混在していないかチェック |
| 429 Too Many Requests | 1 分間に 30 件以上のリクエスト(レートリミット)[^1] | バックオフアルゴリズムを実装し、キューイングで負荷平準化 |
| 500 Internal Server Error | サーバ側障害・一時的通信不良 | 数秒待って再試行。X-Request-ID を添えてサポートに問い合わせ |
テスト環境から本番へ移行する際のチェックリスト {#テスト環境から本番へのチェックリスト}
- キー・URL の置換
API_KEY_TEST → API_KEY_PROD、SECRET_KEY_TEST → SECRET_KEY_PRODを正しく設定。- スコープ確認
- 本番アカウントで同一スコープが有効か管理画面で検証。
- データクリーンアップ
- テスト用
document_id/signing_idが本番に残っていないことを確認(不要なら削除 API でクリア)。 - ログレベル調整
- 開発時は DEBUG、運用時は INFO 以上にし、シークレット情報が出力されないようマスク処理。
- 負荷テスト実施
- 1 分間のリクエスト数を 30 件以下に抑えることを確認(自動リトライ含む)。
セキュリティベストプラクティス {#セキュリティベストプラクティス}
| 項目 | 推奨設定 |
|---|---|
| 通信 | HTTPS(TLS 1.2 以上)必須。証明書の有効期限は自動監視。 |
| シークレット管理 | 環境変数、またはクラウドの Secret Manager に格納し、コードベースにハードコーディングしない。 |
| IP 制限 | 管理画面で自社サーバ IP のみ許可(可能な場合)。 |
| キー定期ローテーション | 90 日ごとに Secret Key を再発行し、古いキーは即削除。 |
| 監査ログ | X-NONCE, X-TIMESTAMP, X-REQUEST-ID は保存し、不正アクセスのトレースを可能にする。 |
レートリミット・課金概要 {#レートリミット課金概要}
| 項目 | 内容 |
|---|---|
| レートリミット | 1 分間あたり 30 リクエスト(全エンドポイント共通)※超過時は 429 が返る[^1] |
| 課金モデル | 月額プラン + 従量課金 ・署名依頼 1 件につき ¥50 (税別) ・テスト環境は月間 1,000 件まで無料枠あり[^2] |
| 請求サイクル | 月末締め、翌月 15 日払い。利用明細は管理画面から CSV ダウンロード可能。 |
FAQ(よくある質問) {#faq}
- テスト環境と本番環境のデータは共有されますか?
-
完全に分離されています。キーもエンドポイントも別物なので、テストデータが本番に流出することはありません。
-
expire_atを過ぎたらどうすれば再送できますか? -
既存の署名依頼はキャンセルし、新しい
expire_at(例:当日から 7 日後)で再度作成してください。 -
PDF 以外に対応しているファイル形式はありますか?
-
現在は PDF、PNG、JPG が公式サポート対象です。その他フォーマットは要問い合わせ。
-
Webhook によるリアルタイム通知は可能ですか?
-
はい。管理画面でエンドポイント URL を登録すると、署名完了・キャンセル等のイベントが POST されます(詳細は API リファレンス参照)。
-
大量署名依頼をバッチ処理したい場合の推奨方法は?
- キューイングサービス(例:Amazon SQS、Google Pub/Sub)でリクエストを蓄積し、1 分間に 30 件以下になるようスロットルして送信します。
参考情報 {#参考情報}
[^1]: GMOサイン公式 API リファレンス – 「レートリミット」セクション(2024‑11 更新)
[^2]: GMOサイン料金ページ – 「無料枠と従量課金」項目(2024‑11 取得)
この記事は 2024 年 12 月に最終更新されています。新機能や仕様変更があった場合は、公式ドキュメントをご確認ください。