API キーと外部サービス連携 — キーが守るもの・ Webhook

この記事は、プログラミングやバイブコーディングをする上で最低限身につけたほうがよい IT の実践的な知識を 1 から身につける「IT 基礎知識講座の一部」です。
API キーは、その呼び出しが誰のものかを示す文字列です。置いてよい場所と、外部サービスの側から呼ばれる Webhook の向きを図解で確かめます。

この記事は、外部サービスを呼び出すときに使う API キー と、外部サービスの側から呼ばれる Webhook の 2 つを扱います。

キーをどこで発行するのか、何を示す文字列なのか、どこに置けば読める人が増えないのかまで確かめます。

  • API キーをどこで発行し、呼び出しのどこに添えるか
  • キーが呼び出しの持ち主を示す文字列であることと、従量課金との関係
  • キーを置いてよい場所と、ブラウザに届く場所に書くと何が起きるか
  • 外部サービスの側から呼ばれるWebhookと、署名の照合

API キーは、使う外部サービスの管理画面で発行する

外部サービスを呼ぶには、まずその提供側からAPI キー(API key。呼び出しがどの利用者のものかを提供側に示す文字列)を受け取ります。

自分で決める値ではなく、使うサービスの側が発行して渡す値です。

決済でも地図でも文章の生成でも、登録する・発行する・自分のサーバーに置くという並びは変わりません。

API キーを発行して、my-app に置くまで
使うサービスのサイトアカウントを作る利用者として登録されるその管理画面新しいキーを発行する長い文字列を1 本受け取る自分のサーバー.env に書く呼び出しに添えて送れる
上から順に進みます。左が作業する場所、真ん中がすること、右がそこでできるものです。キーを作るのは提供側の管理画面で、置き場所は自分のサーバーの中です。

発行された文字列は、その場でコピーして自分のサーバーの .env に置きます。

外部サービスの多くは従量課金(使った分だけ料金がかかる形)で、呼び出した回数や送受信した量に応じて請求されます。

キーが外に出れば、他人が呼び出した分も持ち主の請求に入ります。

API キーは、その呼び出しがどの利用者のものかを示す文字列

決済サービスの API は公開された URL なので、届いた呼び出しが誰のものかを知る手段が要ります。

添える場所は URL ではなく、リクエストヘッダー(request header。本体とは別に、送り主や形式などを名前と値の組で並べる部分)です。

my-app が決済サービスへ送るリクエスト 1 回分
my-app が送るリクエスト 1 回分
1 行目 — メソッドと URL
  • POST https://api.pay.example.com/v1/charges
  • どこへ何をしに行くかだけを書く
2 行目から — リクエストヘッダー
  • API キーを書くのはここ
  • Authorization: Bearer sk_live_a1b2
  • 名前と値の組を 1 行に 1 つ並べる
空行のあと — 本体
  • 送るデータを JSON で入れる
  • 取得だけの呼び出しでは空のことが多い
外枠が 1 回のリクエスト。キーを URL に付けると提供側や自分のサーバーの記録に残るので、真ん中のヘッダーに入れる。

真ん中の枠の 2 行目が、キーを書く 1 行です。

Authorization がヘッダーの名前、Bearer が渡し方を表す語、その後ろがキーの値です。

この名前も書き方も、決めているのは提供側です。

届いたキーで決済サービスが決めるのは、持ち主・許した操作・回数の 3 つです。

my-app がしたことAuthorization ヘッダー決済サービスがすること返るもの
支払いの一覧を取得Bearer sk_live_a1b2持ち主を決めて範囲を確かめる200 と JSON
支払いの一覧を取得付け忘れた持ち主を決められない401 が返る
支払いを 1 件作るBearer sk_read_9f3c読み取りしか許していない403 が返る
短い間に何度も呼ぶBearer sk_live_a1b2持ち主ごとに回数を数える429 が返る

401 ならキーの値とヘッダーの名前を、403 なら許されている操作を確かめます。

4 行目の上限がレート制限(rate limit。一定の時間に受け付ける呼び出しの回数の上限)で、超えると 429 が返ります。

キーは呼び出しの持ち主を示す文字列

API キーは、この呼び出しは誰のものかを伝えるためだけの文字列です。

決済サービスはキーを見て持ち主と許した操作を確かめ、その人の回数を数えるので、キーが無ければ 401、許していない操作なら 403、回数が多すぎれば 429 が返ります。

キーはブラウザに届くコードに置かず、自分のサーバーの中だけに置く

キーを持っている人は誰でも、その持ち主として呼び出せます。

本人かどうかを画面で確かめる仕組みはありません。

my-app のファイルが届く先
my-app 全体
自分のサーバーの中(端末に届かない)
.env
  • PAY_API_KEY=sk_live_a1b2
  • コードとは別のファイルに置く
server.js
  • .env から読んだキーを呼び出しに添える
  • ファイルそのものは端末に送らない
ブラウザに届く範囲(誰でも読める)
  • index.html と script.js
  • 決済サービスを直接呼ばず、自分のサーバーに頼む
  • ブラウザから使う前提で発行されたキーだけを置く
上の枠のファイルは自分のサーバーから動かない。下の枠のファイルは、そのまま利用者の端末に送られる。

同じキーでも、書いた場所によって読める人が変わります。

キーを書いた場所端末に届くか読める人起きること
.env(サーバーの中)届かないサーバーを触れる人だけ契約どおりに使える
script.js届く画面を開いた人全員他人が本人として呼べる
公開した server.js届かないが公開されるコードを見た人全員Git の履歴から消せない

読める人が増える行ほど、他人が本人として呼び出せる状態に近づきます。

3 行目はブラウザには届きませんが、一度 Git に記録すると、あとで消しても変更履歴からは読めます。

手順の「.env に入れてください」は、1 行目の置き方にするための指示です。

外に出てしまったキーにできることは 2 つで、効き目は同じではありません。

外に出てしまったキーに、できること
sk_live_a1b2 が外に出たコードからその行を消す管理画面で失効させる読み取られた値はそのまま使えるそのキーの呼び出しは断られる
左は書いた文字を消すだけ。右は決済サービスの側でキー自体を使えなくする。断られるようになるのは右だけ。

読み取られた値は有効なままで、Git の変更履歴にも残ります。

右が失効(revoke。発行済みのキーを提供側で使えない状態にすること)です。

キーは端末に届かない場所だけに置く

キーは、それを持っている人なら誰でも本人として使えます。

だから利用者の端末に届くファイルにも公開するコードにも書かず、うっかり外に出したときは、書いた文字を消すのではなく失効させて新しいキーを発行し直します。

Webhook は、外部サービスの側から自分のサーバーが呼ばれる仕組み

支払いの完了や返金は、いつ起きるか呼ぶ側には分からない出来事です。

繰り返し尋ねると無駄な呼び出しが増え、気づくのも遅れます。

Webhook(webhook。決められた出来事が起きたときに、外部サービスの側から登録済みの URL へリクエストが送られる仕組み)は、この向きを逆にします。

3 つのやり方で、リクエストを送るのはどちらか
=やり方送るのはどちらあらかじめ用意するものAPI を呼ぶmy-app →決済サービスエンドポイントとAPI キー終わったかを繰り返し尋ねるmy-app →決済サービス尋ねる間隔Webhook決済サービス →my-app通知を受け取るmy-app の URL
上の 2 行は向きが同じなので二重線で結んである。 Webhook だけが、決済サービスから my-app へ送る向き。

3 行目の Webhook だけが、決済サービスから my-app へ送る向きです。

用意するのは、通知を受け取る URL を 1 つ登録しておくことだけです。

1 回の支払いから届く通知
利用者が1 回支払う支払いが完了した領収書ができたあとで返金されたmy-app の/webhooks/payment
1 回の支払いでも、出来事ごとに別の通知が届く。届く先は、登録した 1 つの URL。

支払いが完了したとき、領収書ができたとき、返金されたときに、それぞれ別の通知が届きます。

どの出来事の通知かは、リクエストの本体に書かれています。

矢印 1 は自分から呼ぶ向き、矢印 2 は外部サービスから呼ばれる向きです。

キーが渡るのは決済サービスだけで、利用者の端末には届きません。

Webhook は向きが逆の呼び出し

Webhook は、決済サービスの側から my-app が呼ばれる呼び出しです。

いつ起きるか分からない出来事を起きた時点で知らせてもらうためのもので、用意するのは通知を受け取る URL を 1 つ作って登録しておくことだけです。

通知を受け取る URL は誰でも呼べるので、署名を照合してから処理する

通知を受け取る URL は公開されているので、決済サービス以外の誰かも同じ URL に送れます。

これを防ぐのが署名(signature。送り主と受け取る側だけが知っている文字列から計算した値を添え、受け取る側が同じ計算で照合する仕組み)です。

決済サービスから届いた通知 1 回分
my-app が受け取ったリクエスト 1 回分
1 行目 — メソッドと URL
  • POST https://my-app.example.com/webhooks/payment
  • 自分で用意して登録した URL
2 行目から — リクエストヘッダー
  • 署名が入るのはここ
  • 同じ通知を見分ける ID もここに入ることが多い
空行のあと — 本体
  • どの出来事かが JSON で書かれている
  • 支払いの ID や状態もここに入る
外枠が届いたリクエスト 1 回分。署名はヘッダーに入り、どの出来事かは本体に書かれている。

中身を読む前に、ヘッダーの署名を照合します。

成功を返さなかったときに、送る側が同じ通知をもう一度送ることが再送(retry)です。

通知ごとの ID を記録し、処理済みは読み飛ばします。

照合に使う文字列も API キーと同じくコードに書かず、付け方は API ドキュメントで確かめます。

手順書の「署名を検証してください」は、この照合のことです。

読む前に照合し、2 回目に備える

通知を受け取る URL は公開されているので、届いただけでは送り主が分かりません。

本体を読む前に署名を照合し、合っていたら記録を更新して成功のステータスコードを返し、処理した通知の ID を覚えておいて 2 回目は読み飛ばします。

QUIZ

理解度チェック

まずは1問ずつ答えてみましょう。

Q1リクエストに API キーを添えて送ると、外部サービスは何を決められますか。

Q2API キーをブラウザに届くコードに書くと、何が問題ですか。

Q3Webhook で届いた通知を受け取ったとき、中身を読む前に行うことはどれですか。