> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rc.cleverhub.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhook：リアルタイムの決済通知

> 決済ステータスの変更を POST で受け取るため、Hello Clever の Webhook を設定します。ペイロードのリファレンス、エラーコード、セキュリティ、リトライの挙動を含みます。

Hello Clever の Webhook を使うと、決済のステータスが変わるたびに（たとえば `pending` から `authorised`、または `authorised` から `waiting` へ移ったときに）サーバーがリアルタイムで通知を受け取れます。API をポーリングする代わりにエンドポイントを登録すれば、ステータスが変わるたびに Hello Clever が自動的に POST リクエストを送信します。

## Webhook の仕組み

決済のステータスが変わると、Hello Clever は設定された `endpoint_url` に対して、取引の現在の状態を示す JSON のペイロードとともに `POST` リクエストを送信します。受信を確認するため、エンドポイントは HTTP `200` を返す必要があります。返さない場合、Hello Clever は各試行の間に 15 分の間隔を置いて最大3回リトライします。

<Note>
  リトライによって同じペイロードが複数回配信される可能性があるため、Webhook のエンドポイントでは冪等な処理を実装してください。イベントの重複排除には `uuid` の項目を使ってください。
</Note>

## Webhook の設定

Webhook のエンドポイントは2つの方法で設定できます。

**SDK 連携**：既定の Webhook の URL を登録するには Hello Clever へご連絡ください。SDK 経由で作成されたすべての決済がその URL へ通知を送信します。

**API 連携**：決済作成のリクエストに `webhook_notification` のオブジェクトを含めます。指定した場合、その決済については既定の Webhook の URL を上書きします。

```json theme={null}
{
  "webhook_notification": {
    "endpoint_url": "https://your-domain.com/webhook",
    "authorization_header": "Bearer your-secret-token"
  }
}
```

<ResponseField name="webhook_notification" type="object">
  決済作成のリクエストに含める Webhook の設定です。

  <Expandable>
    <ResponseField name="endpoint_url" type="string" required>
      Hello Clever が Webhook の通知を POST する URL です。
    </ResponseField>

    <ResponseField name="authorization_header" type="string">
      任意。このエンドポイントへのすべての Webhook リクエストの `Authorization` ヘッダーで Hello Clever が送信する値です。送信元を検証できます。
    </ResponseField>
  </Expandable>
</ResponseField>

<a id="payment-statuses" />

## 決済のステータス

Hello Clever は、決済が次のいずれかのステータスに移るたびに Webhook の通知を送信します。

| ステータス                | 説明                                     |
| -------------------- | -------------------------------------- |
| `pending`            | 顧客が新しい決済を開始したが、まだ進めていない状態です。           |
| `authorised`         | 決済がオーソリされ、キャプチャできる状態です。                |
| `waiting`            | 決済が承認され、資金の精算を待っている状態です。               |
| `received`           | 資金を受け取った状態です。                          |
| `expired`            | 完了する前に決済のセッションが失効した状態です。               |
| `return_pending`     | 返金のリクエストが開始され、処理を待っている状態です。            |
| `return_expired`     | 返金のリクエストが失効した状態です（失効期間は 10 日）。         |
| `partially_refunded` | 顧客へ一部の返金が行われた状態です。                     |
| `return_received`    | 顧客へ全額が返金された状態です。                       |
| `return_rejected`    | 返金のリクエストが拒否された状態です。システムはリトライしません。      |
| `failed`             | エラーまたは拒否により決済が失敗した状態です。                |
| `in_dispute`         | 顧客が異議を申立て、審査中の状態です。                    |
| `dispute_lost`       | 異議が顧客の主張どおりに解決した状態です。争われた金額は加盟店へ戻りません。 |

<Note>
  決済が `in_dispute` のステータスにある場合、異議解決の手続きに証拠を提出するため、Hello Clever のサポートへご連絡ください。
</Note>

## Webhook のオブジェクト

各 Webhook の POST のボディには次の項目が含まれます。

| 項目                                          | 型       | 説明                                                                           |
| ------------------------------------------- | ------- | ---------------------------------------------------------------------------- |
| `uuid`                                      | string  | 決済取引の一意の識別子。                                                                 |
| `name`                                      | string  | 顧客の名前。                                                                       |
| `email`                                     | string  | 顧客のメールアドレス。                                                                  |
| `external_id`                               | string  | この決済に対する自社の参照 ID。                                                            |
| `status`                                    | string  | 決済の現在のステータス。                                                                 |
| `pay_code`                                  | object  | 3DS が必要な場合は `3ds_url` を含みます。決済が失敗した場合は `error_code` と `error_message` を含みます。 |
| `pay_code.3ds_url`                          | string  | 顧客が 3DS の認証を完了するための URL（必要な場合）。                                              |
| `pay_code.error_code`                       | string  | エラーまたは拒否のコード（例：`issuer_declined`）。                                           |
| `pay_code.error_message`                    | string  | 人が読めるエラーまたは拒否のメッセージ。                                                         |
| `currency`                                  | string  | 取引の通貨コード (ISO 4217)。                                                         |
| `amount`                                    | string  | 元の決済金額。                                                                      |
| `total`                                     | string  | 手数料を含む合計金額。                                                                  |
| `paid_amount`                               | string  | 支払われた金額。                                                                     |
| `is_refundable`                             | boolean | 決済が返金可能かどうか。                                                                 |
| `payment_method`                            | string  | 使用された決済手段（例：`card`）。                                                         |
| `expired_at`                                | string  | 決済 URL が失効する日時 (UTC)。                                                        |
| `webhook_notification`                      | object  | Webhook の設定情報。                                                               |
| `webhook_notification.endpoint_url`         | string  | Webhook の送信先 URL。                                                            |
| `webhook_notification.authorization_header` | string  | マスクされた authorization ヘッダーの値。                                                 |
| `refund_information`                        | object  | 返金に関する情報。                                                                    |
| `refund_information.total_amount`           | string  | 返金可能な合計金額。                                                                   |
| `refund_information.refund_amount`          | string  | 返金された金額。                                                                     |
| `refund_information.description`            | string  | 返金の理由。                                                                       |
| `sender_details`                            | object  | 決済手段の詳細。                                                                     |
| `sender_details.card`                       | object  | カード決済の詳細。                                                                    |
| `sender_details.card.card_type`             | string  | 使用されたカードの種類。                                                                 |
| `sender_details.card.card_brand`            | string  | カードのブランド（例：`visa`、`mastercard`）。                                             |
| `sender_details.card.card_last_4`           | string  | カード番号の下4桁。                                                                   |
| `created_at`                                | string  | 決済が作成された日時。                                                                  |
| `token`                                     | object  | カードのトークンの詳細（ステータスが `authorised` または `waiting` の場合のみ含まれます）。                   |
| `token.id`                                  | string  | カードの再利用可能なトークン（形式：`tok_...`）。                                                |
| `token.type`                                | string  | トークンの種類（例：`card`）。                                                           |

### ペイロードの例

```json theme={null}
{
  "uuid": "QVABPPC7",
  "name": "Hello Clever",
  "email": "test@example.com",
  "external_id": "123",
  "status": "authorised",
  "pay_code": {
    "3ds_url": "https://3ds-example.com"
  },
  "currency": "USD",
  "amount": "100.0",
  "total": "100.0",
  "paid_amount": "0.0",
  "is_refundable": false,
  "payment_method": "card",
  "expired_at": "",
  "webhook_notification": {
    "endpoint_url": "https://webhook.site/12da7803-c4cf-4f32-812d-aaeaecf20d9d",
    "authorization_header": "****"
  },
  "refund_information": {
    "total_amount": "250.0",
    "refund_amount": "10.0",
    "description": "Testing refund"
  },
  "sender_details": {
    "card": {
      "card_type": "card",
      "card_brand": "visa",
      "card_last_4": "1111",
      "card_country": "US"
    }
  },
  "created_at": "2025-05-30T05:11:17.602+0000",
  "token": {
    "id": "tok_dfe1988a1ffc0d6562d3",
    "type": "card"
  }
}
```

## オーソリ済みカードのトークン

カード決済が `authorised` または `waiting` のステータスに達すると、Hello Clever は Webhook のペイロードに `token` のオブジェクトを含めます。このトークンは安全に保管してください。同じ顧客に対して、カード情報の再入力を求めずに将来の決済を作成できます。

```json theme={null}
{
  "token": {
    "id": "tok_773085396b86562040f4",
    "type": "card"
  }
}
```

<Warning>
  `token` のオブジェクトが Webhook の通知に含まれるのは、ステータスが `authorised` または `waiting` に変わったときのみです。それ以外のステータスの通知には含まれません。
</Warning>

## Webhook のセキュリティ

Hello Clever は2つのセキュリティの仕組みに対応しており、単独でも併用でも利用できます。

### Authorization ヘッダー

Webhook の設定で `authorization_header` を指定すると、Hello Clever はエンドポイントへのすべての Webhook リクエストの `Authorization` ヘッダーでその値を送信します。

```
Authorization: Bearer your-secret-token
```

サーバー側でこのヘッダーを既知のシークレットと照合し、リクエストが Hello Clever からのものであることを確認してください。

### HTTP Webhook 署名 (HMAC-SHA256)

Hello Clever は各 Webhook のペイロードを HMAC-SHA256 で署名し、その署名を `HTTP-WEBHOOK-SIGNATURE` のヘッダーで送信します。

```
HTTP-WEBHOOK-SIGNATURE: ff456ed178c487a2fd323043315320f8ec0908327a1b76f1ad54d9f81a000f9d
```

署名を検証するには、Webhook Secret Key を使って**生のリクエストボディ**の HMAC-SHA256 を再計算し、ヘッダーの値と比較してください。

<Steps>
  <Step title="Webhook Secret Key を確認する">
    Merchant Portal で **Dashboard → Developer → Authentication → Webhook タブ**へ移動し、**Webhook secret key** の欄の値をコピーします。
  </Step>

  <Step title="想定される署名を計算する">
    生のリクエストボディ（JSON をパースして再度文字列化しないこと）と Webhook Secret Key を使います。

    <CodeGroup>
      ```javascript Node.js theme={null}
      const crypto = require('crypto');

      function verifySignature(rawBody, webhookSecretKey, receivedSignature) {
        const expectedSignature = crypto
          .createHmac('sha256', webhookSecretKey)
          .update(rawBody, 'utf8')
          .digest('hex');
        return receivedSignature === expectedSignature;
      }

      // In your request handler:
      const receivedSignature = req.headers['http-webhook-signature'];
      const isValid = verifySignature(req.rawBody, process.env.WEBHOOK_SECRET, receivedSignature);
      ```

      ```python Python theme={null}
      import hmac
      import hashlib

      def verify_signature(raw_body: str, webhook_secret_key: str, received_signature: str) -> bool:
          expected_signature = hmac.new(
              webhook_secret_key.encode('utf-8'),
              raw_body.encode('utf-8'),
              hashlib.sha256
          ).hexdigest()
          return received_signature == expected_signature

      # In your request handler:
      received_signature = request.headers.get('HTTP-WEBHOOK-SIGNATURE')
      is_valid = verify_signature(request.get_data(as_text=True), WEBHOOK_SECRET, received_signature)
      ```

      ```java Java theme={null}
      import javax.crypto.Mac;
      import javax.crypto.spec.SecretKeySpec;
      import java.nio.charset.StandardCharsets;

      public boolean verifySignature(String rawBody, String secret, String receivedSignature) throws Exception {
          Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
          SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
          sha256_HMAC.init(secretKey);
          byte[] hash = sha256_HMAC.doFinal(rawBody.getBytes(StandardCharsets.UTF_8));
          StringBuilder hexString = new StringBuilder();
          for (byte b : hash) {
              String hex = Integer.toHexString(0xff & b);
              if (hex.length() == 1) hexString.append('0');
              hexString.append(hex);
          }
          return hexString.toString().equals(receivedSignature);
      }
      ```
    </CodeGroup>
  </Step>

  <Step title="比較して受け入れるか拒否する">
    署名が一致すれば、ペイロードは正当で改変されていません。異なる場合はリクエストを拒否してください。改ざんされている、または信頼できない送信元から送られた可能性があります。
  </Step>
</Steps>

<Tip>
  セキュリティを最大化するには、`Authorization` ヘッダーと `HTTP-WEBHOOK-SIGNATURE` の両方を検証してください。`Authorization` ヘッダーは送信者を確認し、署名はペイロードが改ざんされていないことを確認します。
</Tip>

## エラー処理とリトライ

エンドポイントが HTTP `200` を返さない場合、Hello Clever は各試行の間に **15 分の間隔**を置いて、Webhook の呼び出しを最大 **3 回**リトライします。

リトライを正しく扱うには次のようにしてください。

* 重い処理を行う前に、Webhook を受け取った時点で直ちに `200` を返してください。
* タイムアウトを避けるため、イベントは非同期に処理してください（キューを使うなど）。
* 重複した配信を検出して破棄するため、`uuid` の項目を使ってください。

## エラーコード

決済が失敗すると、`pay_code` のオブジェクトに `error_code` と `error_message` が含まれます。次の表はすべてのエラーコードの一覧です。

| エラーコード                             | メッセージ                                       |
| ---------------------------------- | ------------------------------------------- |
| `account_closed`                   | 顧客の銀行口座が閉鎖されています。                           |
| `amount_invalid`                   | 決済金額が無効か、許可された金額を超えています。                    |
| `amount_too_large`                 | 指定された金額が許容される上限を超えています。より低い金額で再試行してください。    |
| `amount_too_small`                 | 指定された金額が許容される下限を下回っています。より高い金額で再試行してください。   |
| `authentication_expired`           | カードのオーソリが失効しました。                            |
| `authentication_failed`            | 決済をオーソリできません。                               |
| `authentication_required`          | 3D セキュアなどの認証が必要な取引のため、カードが拒否されました。          |
| `capture_not_authorised`           | キャプチャの前に、取引が `authorised` のステータスである必要があります。 |
| `card_decline_rate_limit_exceeded` | このカードは拒否された回数が多すぎます。24 時間後に再度請求を試せます。       |
| `card_expired`                     | カードの有効期限が切れています。                            |
| `card_lost`                        | カードが紛失として届出されているため、決済が拒否されました。              |
| `card_not_supported`               | このカードはこの種類の購入に対応していません。                     |
| `card_number_incorrect`            | カード番号が正しくありません。                             |
| `card_restricted`                  | 顧客はこのカードでこの決済を行えません。                        |
| `card_stolen`                      | カードが盗難として届出されているため、決済が拒否されました。              |
| `card_velocity_exceeded`           | 顧客がカードの残高、利用枠、または取引金額の上限を超えました。             |
| `currency_not_supported`           | このカードは指定された通貨に対応していません。                     |
| `customer_canceled`                | 顧客が銀行を通じて決済を停止しました。                         |
| `cvc_incorrect`                    | CVC の番号が正しくありません。                           |
| `debit_not_authorised`             | 顧客が銀行に対して、この決済が承認されていないものだと申告しました。          |
| `expiry_month_invalid`             | 有効期限の月が無効です。                                |
| `expiry_year_invalid`              | 有効期限の年が無効です。                                |
| `fraudulent`                       | 不正の疑いがあるため、決済が拒否されました。                      |
| `generic_declined`                 | 理由不明でカードが拒否された、または決済がブロックされました。             |
| `insufficient_funds`               | 購入を完了するための残高がカードに不足しています。                   |
| `issuer_declined`                  | 理由不明でカードが拒否されました。                           |
| `issuer_not_available`             | カード発行会社に到達できなかったため、決済をオーソリできませんでした。         |
| `pin_required`                     | PIN が必要なため、カードが拒否されました。                     |
| `pin_try_exceeded`                 | PIN の試行回数の上限を超えました。                         |
| `processing_error`                 | カードの処理中にエラーが発生しました。後で再試行するか、別の決済手段をお試しください。 |
| `return_url_invalid`               | Return URL が無効です。                           |
| `transaction_is_blocked`           | 取引がブロックされています。すでに支払われている場合は返金されます。          |
| `transaction_duplicated`           | 同一の内容の取引が最近送信されました。                         |
| `withdrawal_count_limit_exceeded`  | 顧客がカードで利用できる残高または利用枠を超えました。                 |

## ベストプラクティス

1. 処理を行う前に、すべての受信リクエストで `Authorization` ヘッダーまたは `HTTP-WEBHOOK-SIGNATURE` を検証してください。
2. リトライによる副作用の重複を防ぐため、`uuid` の項目を使って冪等性を実装してください。
3. リクエストを受け取ったら直ちに `200 OK` を返し、ペイロードは非同期に処理してください。
4. Webhook のエンドポイントの URL は非公開に保ち、公開の場での共有は避けてください。
5. 通信中のペイロードを保護するため、Webhook のエンドポイントには HTTPS を使ってください。
6. 開発中は [webhook.site](https://webhook.site) や [ngrok](https://ngrok.com) のようなローカルトンネルを使ってエンドポイントをテストしてください。


## Related topics

- [リアルタイム A2A 決済の詳細](/ja/platform-overview/payment-concepts/real-time-a2a.md)
- [リアルタイム決済とカード決済の比較](/ja/platform-overview/payment-concepts/rtp-vs-cards.md)
- [Hello Clever におけるリアルタイム決済のセキュリティ](/ja/security/real-time-payments-security.md)
