Webhook

Webhook とは、あるサービスで発生したイベントの通知を、HTTP 経由の外部 URL で受け取る仕組みのことです。 Webhook を使うと、以下のような PAY.JP で起きたイベントを、任意の URL に対して通知できます。

  • 支払いが正常に行われたとき
  • 定期課金による引き落としが行われ、期間が更新されたとき
  • 定期課金がキャンセルされたとき

PAY.JP の管理画面から送信先 URL を追加するだけで、上記のようなイベントが自動的に通知されるようになります。

活用シーン

PAY.JP の機能には、支払い作成リクエストのようにクライアントからの同期的なリクエスト・レスポンスによって結果が得られるものと、定期課金の更新のように PAY.JP サーバーが契機となって行われる操作があります。こういった非同期的なイベントが行われたことを検知するための仕組みとして、Webhook は有用です。

Webhook を使用せずに加盟店システム上の状態と PAY.JP 上の状態を同期する場合、対象のオブジェクトやイベントの定期的な走査が必要となり、イベントの同期性も失われてしまいます。そのため、定期課金・テナントなどの機能を利用する際は、Webhook による通知を実装することを推奨します。

ただし、Webhook は HTTP リクエストであるため、突発的な要因によって不達、多重送信などが起こりえます。Webhook を主なイベント検知手段として使いつつ、不達・多重送信によって致命的な問題が発生しないような実装を行う必要があります。

実装Howtoもあわせてご参照ください。

設定

PAY.JP にログインして、下記の管理画面から Webhook の送信先を追加できます。 URL を入力し、イベントが送信されるモードをテストモードか本番モードかで選択し、追加を押せば完了です。 Webhook 送信先は複数追加することが可能です。 また、後述する各種イベントのテスト送信も行うことができます。

Webhookの設定

リクエスト形式

PAY.JP の Webhook はすべて POST で送信され、リクエストボディーには、イベントの JSON データが含まれています。

レスポンス要件

レスポンスの HTTP ステータスは 200 である必要があります。 PAY.JP はステータスコード以外は無視するため、レスポンスボディー等は空白でも問題ありません。

イベント

以下は実際に PAY.JP から送信されるイベントの種類です。それぞれの内容に応じて、イベントの JSON が送信されます。 type はイベントの JSON データに含まれます。

type 内容
charge.succeeded 支払いの成功
charge.failed 支払いの失敗
charge.updated 支払いの更新
charge.refunded 支払いの返金
charge.captured 支払いの確定
dispute.created チャージバック発生
token.created トークンの作成
customer.created 顧客の作成
customer.updated 顧客の更新
customer.deleted 顧客の削除
customer.card.created 顧客のカード作成
customer.card.updated 顧客のカード更新
customer.card.deleted 顧客のカード削除
plan.created プランの作成
plan.updated プランの更新
plan.deleted プランの削除
subscription.created 定期課金の作成
subscription.updated 定期課金の更新
subscription.deleted 定期課金の削除
subscription.paused 定期課金の停止
subscription.resumed 定期課金の再開
subscription.canceled 定期課金のキャンセル
subscription.renewed 定期課金の期間更新
transfer.succeeded 入金内容の確定 (通常加盟店、プラットフォーマー)
tenant.created テナント作成(PAY.JP Platformのみ)
tenant.deleted テナント削除(PAY.JP Platformのみ)
tenant.updated テナント情報の更新、本番申請(初回・更新含む)、弊社による審査結果反映(PAY.JP Platformのみ)
tenant_transfer.succeeded テナントの入金内容の確定(PAY.JP Platformのみ)

下記は、5000円の支払いが成功したときに Webhook で送信されるイベントのデータ例です。

{
  "created": 1442212986,
  "data": {
    "amount": 5000,
    "amount_refunded": 0,
    "captured": true,
    "captured_at": 1442212986,
    "card": {
      "address_city": null,
      "address_line1": null,
      "address_line2": null,
      "address_state": null,
      "address_zip": null,
      "address_zip_check": "unchecked",
      "brand": "Visa",
      "country": null,
      "created": 1442212986,
      "customer": null,
      "cvc_check": "passed",
      "exp_month": 1,
      "exp_year": 2016,
      "fingerprint": "e1d8225886e3a7211127df751c86787f",
      "id": "car_f0984a6f68a730b7e1814ceabfe1",
      "last4": "4242",
      "name": null,
      "object": "card"
    },
    "created": 1442212986,
    "currency": "jpy",
    "customer": null,
    "description": null,
    "expired_at": null,
    "failure_code": null,
    "failure_message": null,
    "id": "ch_bcb7776459913c743c20e9f9351d4",
    "livemode": false,
    "object": "charge",
    "paid": true,
    "refund_reason": null,
    "refunded": false,
    "subscription": null
  },
  "id": "evnt_5328acdbdb5294d6fc9cc903f8c",
  "livemode": false,
  "object": "event",
  "pending_webhooks": 1,
  "type": "charge.succeeded"
}

data にはイベントの詳細内容が入ります。API で返ってくるレスポンスと同様の JSON データです。 pending_webhooks では、まだ Webhook が正常に受信されていない送信先 URL の数を示しています。

リトライ

Webhook の送信先から 4xx5xx のステータスコードが返ってくる場合、Webhook は自動的にリトライを行います。リトライは3分間隔で、最大3回行われます。

安全性

本番環境で Webhook を使用する場合は、必ず HTTPS 経由で受け取るようにしてください。Webhook では、顧客情報や支払いの詳細データなども送信されるため、HTTPS による暗号化通信を強く推奨いたします。

正当性

PAY.JP からのすべての Webhook には X-Payjp-Webhook-Token がヘッダーに含まれています。この値は下記のようなアカウント固有のトークン ID となっています

X-Payjp-Webhook-Token: whook_a09d5c1c87be4e1590a9f16516

Webhook を受け取るアプリケーション側で、この値が正しいかどうかを判別することで、PAY.JP からの Webhook であるか正当性を確認できます。 自分のアカウントの X-Payjp-Webhook-Token を確認するには、PAY.JP にログインして アカウント設定 から確認できます。

APIリファレンス - Event(イベント)

実装Howto

Webhook は有用な機能ですが、トラブルなく運用するためにはいくつかのポイントを押さえておくことが重要です。
すべてを実装しない限り利用できないというものではありませんが、トラブルを未然に防ぐため、ご利用のシステムの要件に照らしての十分なご検討をお願いいたします。

受信記録

加盟店側の Webhook エンドポイントへのリクエストログやエラーログを記録することは、不達や多重送信などが疑われる際の問題切り分けの第一歩となります。確実に記録することを強く推奨いたします。加えて、受信した Webhook はそのデータを一定時間保管しておくことを推奨いたします。

受信したにもかかわらず、加盟店側システム上で例外が発生した等の理由で正常に処理できなかった場合、データが残っていないと管理画面からの再送等でリカバリをする必要が出てしまいます。記録されたログデータから Webhook 受信時の処理を再現できるような仕組みがあれば、より万全といえるでしょう。

多重送信への対応

さまざまな要因により、同一の Webhook が複数回送信されることがありえます。多重送信時に問題とならないような仕組みを検討してください。

  • 受け取ったイベント ID を一定時間キャッシュ等に記録しておき、同一イベントを受信した場合スキップする。
  • 同じイベントを複数回処理しても状態が同一になるようなロジック(べき等)にする。

不達への対応

Webhook が送られてきていないと思われる事象が発生した場合、まずは上述のリクエストログや管理画面の Webhook 送信先設定等から、受信側システムに不具合が生じていないかをご確認ください。

Webhook受信エンドポイントのサーバーエラーやSSL証明書の期限切れといった原因によって、リトライ上限までリトライしても送信が完了しなかったという不達事例がよく報告されています。

実際に送信された形跡が確認できない場合、次点の原因として送信遅延が考えられます。遅延が発生する要因としては以下のようなものが挙げられます。

  • 月末月初や毎時0分付近など、多数の加盟店によるリクエスト・定期課金が集中する時間帯
  • PAY.JP のメンテナンスによる計画停止中
  • システム障害中

とくに月末・月初は多数の定期課金更新が集中していることで大きく遅延する傾向にあるため、通常よりも数時間程度の遅延を考慮した設計としていただくことを推奨いたします。

受け取り側でのエラーや遅延ではないと考えられる場合は、管理画面からの Webhook の再送をご検討ください。

管理画面からの再送が難しい場合、実際のデータからのリカバリを検討します。ご利用の形態にも左右されるため統一的な方針を示すのは難しいものとなっておりますが、概ね以下のような方針が考えられます。

オブジェクト更新系のイベント(subscription.renewedなど)

一例として、下記のような月額課金制の会員システムにおいて定期課金を利用する場合のリカバリ処理例を示します。

  • エンドユーザーは月額会員として登録し、毎月定額の利用料を支払ってサービスを受ける。
  • 加盟店システム上ではユーザーを 会員 レコードとしてデータベースに記録し、月額利用料を PAY.JP の定期課金機能で徴収する。

このシステムにおいて、月額利用料の定期課金のイベントが想定される期間内に受信できていないことを検知するためには、下記のような方法が考えられます。

  1. それぞれの 会員 レコードに、 自身の月額利用料を処理している定期課金のID最後に定期課金のイベントを受け取った日時 のフィールドを記録する。
  2. 最後に定期課金のイベントを受け取った日時 が1か月+1日以上前である 会員 レコードを抽出する。
  3. 抽出されたそれぞれの 会員 について、定期課金情報を取得 API から、現在の定期課金の状態を取得し、必要に応じてリカバリする。

オブジェクト新規作成系のイベント(dispute.createdなど)

PAY.JP 側で新しく作成されるオブジェクトの場合、加盟店システム側であらかじめ ID を知ることができません。これは、先に説明した月額課金の例と同じです。そのため、対象となるオブジェクトやイベントの一覧 API を使って、最新のオブジェクトリストを取得する必要があります。そして、そのリストを加盟店システム上のデータと比較することが求められます。