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

# Webhooks

> Webhooks 允许您设置通知系统，用于接收对 Blockradar API 发出的某些请求的更新。

<Note>
  概述<br />
  Webhooks 允许您在事件发生时接收实时通知，而无需轮询 API。这对于跟踪充值、提现和其他交易状态非常重要。
</Note>

## 前提条件

在配置 Webhooks 之前，请确保您已完成以下步骤：

<Steps>
  <Step title="API 密钥">
    从 [Blockradar 控制面板](https://dashboard.blockradar.co) 获取您的 API 密钥。导航至 **Developers** 生成密钥。
  </Step>

  <Step title="创建钱包">
    通过 [创建钱包 API](/zh/api-reference/wallets/create-wallet) 或控制面板创建主钱包。
  </Step>

  <Step title="Webhook URL 端点">
    准备一个可公开访问的 HTTPS 端点来接收 Webhook 事件。
  </Step>

  <Step title="配置 Webhook URL">
    通过控制面板将您的 Webhook URL 添加到主钱包。
  </Step>
</Steps>

## 简介

通常，当您向 API 端点发出请求时，您期望获得近乎即时的响应。但是，某些请求可能需要较长时间处理，这可能导致超时错误。为了防止超时错误，会返回一个待处理响应。由于您的记录需要更新为请求的最终状态，您需要：

1. 请求更新（通常称为轮询），或
2. 使用 Webhook URL 监听事件。

<Tip>
  实用提示<br />
  我们建议您使用 Webhook 为客户提供价值，而不是使用回调或轮询。使用回调时，我们无法控制客户端发生的情况。您也无法控制。如果客户设备上的网络连接失败或较弱，或者设备在交易后关闭，回调可能会失败。
</Tip>

## 轮询 vs Webhooks

轮询需要定期发出 `GET` 请求以获取请求的最终状态。例如，当您为客户分配充值地址时，您需要不断请求与该地址关联的交易，直到找到一个。

使用 Webhooks，资源服务器（在本例中为 Blockradar）会在您请求的状态发生变化时向您的服务器发送更新。请求状态的变化称为事件。您通常会在称为 Webhook URL 的 POST 端点上监听这些事件。

下表突出显示了轮询和 Webhooks 之间的一些差异：

|           | **轮询** | **Webhooks** |
| --------- | ------ | ------------ |
| **更新方式**  | 手动     | 自动           |
| **速率限制**  | 是      | 否            |
| **受扩展影响** | 是      | 否            |

## 创建 Webhook URL

Webhook URL 只是资源服务器发送更新的 POST 端点。该 URL 需要解析 JSON 请求并返回 `200 OK`：

<CodeGroup>
  ```javascript webhook.js theme={null}
  // Using Express
  app.post("/my/webhook/url", function(req, res) {
      // Retrieve the request's body
      const event = req.body;
      // Do something with event
      res.send(200);
  });
  ```

  ```php webhook.php theme={null}
  <?php
  // Retrieve the request's body and parse it as JSON
  $input = file_get_contents("php://input");
  $event = json_decode($input, true);

  // Check for JSON parsing errors
  if (json_last_error() !== JSON_ERROR_NONE) {
      http_response_code(400); // Bad Request
      echo json_encode(["error" => "Invalid JSON"]);
      exit();
  }

  // Do something with $event
  // For example, log the event or process it
  // log_event($event);

  // Send a successful response
  http_response_code(200); // HTTP status code 200 OK
  echo json_encode(["status" => "success"]);
  ?>
  ```
</CodeGroup>

当您的 Webhook URL 收到事件时，需要解析并确认事件。确认事件意味着在 HTTP 头中返回 `200 OK`。如果响应头中没有 `200 OK`，我们将在接下来的 2 小时 35 分钟内持续发送事件：

* 我们将尝试发送 5 次 Webhook，每次尝试之间的延迟递增，从 5 分钟开始并以指数方式增加到 80 分钟。这 5 次尝试的总持续时间约为 2 小时 35 分钟。

<Warning>
  避免长时间运行的任务<br />
  如果您的 Webhook 函数中有长时间运行的任务，您应该在执行长时间运行的任务之前确认事件。长时间运行的任务会导致请求超时和服务器自动返回错误响应。没有 200 OK 响应，我们将按上述方式重试。
</Warning>

## 本地测试 Webhooks

在开发过程中，您可以在测试主钱包环境中将 Webhook URL 配置为本地地址，例如 `http://localhost` 或 `127.0.0.1`。

要将本地服务器暴露到互联网以进行测试，请考虑使用 [ngrok](https://ngrok.com/) 或 [webhook.site](https://webhook.site) 等工具。这些工具允许您查看 Webhook 载荷的样子，并在将应用程序部署到正式环境之前在本地模拟 Webhook 事件。

使用这些工具，您可以确保 Webhook 集成正常工作，并在进入生产环境之前在受控的本地环境中处理任何问题。

### 重新发送交易 Webhook

如果由于某些原因您没有收到交易 Webhook 且退避时间已过，我们提供了一个 API 供您重新发送交易 Webhook

<Warning>
  谨慎使用！<br />
  这可能导致处理已完成的交易，请确保在使用此端点时进行适当的验证。
</Warning>

<CodeGroup>
  ```bash resend.cmd theme={null}
  curl --request POST \
    --url https://api.blockradar.co/v1/wallets/{walletId}/transactions/webhooks/resend \
    --header 'Content-Type: application/json' \
    --header 'x-api-key: <api-key>' \
    --data '{
    "id": "TRANSACTION_ID"
  }'
  ```

  ```js resend.js theme={null}
  const options = {
    method: 'POST',
    headers: {'x-api-key': '<api-key>', 'Content-Type': 'application/json'},
    body: '{"id":"TRANSACTION_ID"}'
  };

  fetch('https://api.blockradar.co/v1/wallets/{walletId}/transactions/webhooks/resend', options)
    .then(response => response.json())
    .then(response => console.log(response))
    .catch(err => console.error(err));
  ```
</CodeGroup>

## 验证事件来源

由于您的 Webhook URL 是公开可用的，您需要验证事件是否来自 Blockradar 而不是恶意攻击者。有两种方法可以确保 Webhook URL 的事件来自 Blockradar：

1. 签名验证（推荐）

### 签名验证

从 Blockradar 发送的事件带有 x-blockradar-signature 头。此头的值是使用您的密钥对事件载荷进行 HMAC SHA512 签名的结果。应在处理事件之前验证头签名：

<CodeGroup>
  ```js signature.js theme={null}
  const crypto = require('crypto');
  const apiKey = process.env.WALLET_API_KEY;
  // Using Express
  app.post("/my/webhook/url", function(req, res) {
      //validate event
      const hash = crypto.createHmac('sha512', apiKey).update(JSON.stringify(req.body)).digest('hex');
      if (hash == req.headers['x-blockradar-signature']) {
      // Retrieve the request's body
      const event = req.body;
      // Do something with event
      }
      res.send(200);
  });
  ```

  ```php signature.php theme={null}
  <?php

  // Retrieve the request's body
  $body = file_get_contents('php://input');

  // Get the API key from the environment
  $apiKey = getenv('WALLET_API_KEY');

  // Using HMAC-SHA512 for hashing
  $signature = hash_hmac('sha512', $body, $apiKey);

  // Check if the signature matches the one provided in the headers
  if ($signature === $_SERVER['HTTP_X_BLOCKRADAR_SIGNATURE']) {
      // The signature is valid, proceed with processing the event
      $event = json_decode($body, true);
      // Do something with $event
  }

  // Send a 200 OK response
  http_response_code(200);
  ```

  ```python signature.py theme={null}
  import hmac
  import hashlib
  import json
  from flask import Flask, request, jsonify

  app = Flask(__name__)

  @app.route("/my/webhook/url", methods=["POST"])
  def webhook():
      # Retrieve the request's body
      body = request.get_data()

      # Get the API key
      api_key = os.environ.get('WALLET_API_KEY')

      # Using HMAC-SHA512 for hashing
      signature = hmac.new(api_key.encode('utf-8'), body, hashlib.sha512).hexdigest()

      # Check if the signature matches the one provided in the headers
      if signature == request.headers.get('X-BlockRadar-Signature'):
          # The signature is valid, proceed with processing the event
          event = json.loads(body.decode('utf-8'))
          # Do something with event

      # Send a 200 OK response
      return '', 200
  ```
</CodeGroup>

## 上线检查清单

现在您已成功创建 Webhook URL，以下是确保您获得良好体验的一些方法：

* 在 [Blockradar](https://dashboard.blockradar.co) 控制面板的主钱包上添加 Webhook URL
* 确保您的 Webhook URL 是公开可用的（localhost URL 无法接收事件）
* 如果使用 `.htaccess`，请记住在 URL 末尾添加尾部 `/`
* 测试您的 Webhook 以确保您获取 JSON 主体并返回 `200 OK` HTTP 响应
* 如果您的 Webhook 函数有长时间运行的任务，您应该首先通过返回 `200 OK` 确认收到 Webhook，然后再继续长时间运行的任务
* 如果我们没有从您的 Webhook 收到 `200 OK` HTTP 响应，我们会将其标记为失败尝试
* 我们将尝试发送 5 次 Webhook，每次尝试之间的延迟递增，从 5 分钟开始并以指数方式增加到 80 分钟。这 5 次尝试的总持续时间约为 2 小时 35 分钟。

## 事件类型

以下是我们目前触发的事件。随着我们在未来接入更多操作，我们会将更多事件添加到此列表中。

## 事件示例

以下是一些最常见事件的 Webhook 载荷示例：

<Tabs>
  <Tab title="充值成功">
    ```json theme={null}
    {
        "event": "deposit.success",
        "data": {
            "id": "6d2f9646-cae4-48a5-8bfe-1f9379868d4f",
            "reference": "LSk5RLfSrR",
            "senderAddress": "0x2455eC6700092991Ce0782365A89d5Cd89c8Fa22",
            "recipientAddress": "0xe1037B45b48390285e5067424053fa35c478296b",
            "amount": "10.0",
            "amountPaid": "10.0",
            "fee": null,
            "currency": "USD",
            "blockNumber": 6928760,
            "blockHash": "0x5f2e0ed782752b9559e7a3d89c0fb9f6706e4866e74ba7a434cf933bb3f02a2b",
            "hash": "0x94c733496df59c15e5a489f20374096bba31166a8e149ceea4d410e3e5821357",
            "confirmations": 6,
            "confirmed": true,
            "gasPrice": "1201381238",
            "gasUsed": "62159",
            "gasFee": "0.000074676656372842",
            "status": "SUCCESS",
            "type": "DEPOSIT",
            "note": null,
            "amlScreening": {
                "provider": "ofac",
                "status": "success",
                "message": "Address is not sanctioned"
            },
            "network": "testnet",
            "chainId": 11155111,
            "metadata": {
                "user_id": 1
            },
            "createdAt": "2024-10-23T11:19:58.451Z",
            "updatedAt": "2024-10-23T11:19:58.451Z",
            "asset": {
                "id": "fe04a28c-c615-4e41-8eda-f84c862864f5",
                "name": "USDC Coin",
                "symbol": "USDC",
                "decimals": 6,
                "address": "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
                "standard": "ERC20",
                "isActive": true,
                "logoUrl": "https://res.cloudinary.com/blockradar/image/upload/v1716800083/crypto-assets/usd-coin-usdc-logo_fs9mhv.png",
                "network": "testnet",
                "createdAt": "2024-05-14T11:53:33.682Z",
                "updatedAt": "2024-06-14T22:32:12.589Z"
            },
            "address": {
                "id": "0a69c48a-6c6f-422c-bd6a-70de3306a3ac",
                "address": "0xe1037B45b48390285e5067424053fa35c478296b",
                "name": "Customer 1",
                "isActive": true,
                "type": "INTERNAL",
                "derivationPath": "m/44'/60'/0'/0/87",
                "metadata": {
                    "user_id": 1
                },
                "network": "testnet",
                "createdAt": "2024-10-23T11:13:40.446Z",
                "updatedAt": "2024-10-23T11:13:40.446Z"
            },
            "blockchain": {
                "id": "85ffc132-3972-4c9e-99a5-5cf0ccb688bf",
                "name": "ethereum",
                "symbol": "eth",
                "slug": "ethereum",
                "derivationPath": "m/44'/60'/0'/0",
                "isEvmCompatible": true,
                "logoUrl": "https://res.cloudinary.com/blockradar/image/upload/v1716800081/crypto-assets/ethereum-eth-logo_idraq2.png",
                "isActive": true,
                "tokenStandard": "ERC20",
                "createdAt": "2024-05-14T11:53:33.095Z",
                "updatedAt": "2024-06-14T22:32:11.983Z"
            },
            "wallet": {
                "id": "d236a191-c1d4-423c-a439-54ce6542ca41",
                "name": "Ethereum Master Wallet",
                "description": "This is ethereum testnet master wallet",
                "address": "0x947514e4B803e312C312da0F1B41fEDdbe15ae7a",
                "derivationPath": "m/44'/60'/0'/0/0",
                "isActive": true,
                "status": "ACTIVE",
                "network": "testnet",
                "createdAt": "2024-08-22T09:48:56.322Z",
                "updatedAt": "2024-10-23T10:52:34.332Z"
            },
            "beneficiary": null
        }
    }
    ```
  </Tab>

  <Tab title="充值处理中">
    ```json theme={null}
    {
        "event": "deposit.processing",
        "data": {
            "id": "6d2f9646-cae4-48a5-8bfe-1f9379868d4f",
            "reference": "LSk5RLfSrR",
            "senderAddress": "0x2455eC6700092991Ce0782365A89d5Cd89c8Fa22",
            "recipientAddress": "0xe1037B45b48390285e5067424053fa35c478296b",
            "amount": "10.0",
            "status": "PROCESSING",
            "type": "DEPOSIT",
            "network": "testnet"
        }
    }
    ```
  </Tab>

  <Tab title="提现成功">
    ```json theme={null}
    {
        "event": "withdraw.success",
        "data": {
            "id": "081d6315-159f-4c38-b02a-c4708836c5bd",
            "reference": "y8lvy55cK",
            "senderAddress": "0x947514e4B803e312C312da0F1B41fEDdbe15ae7a",
            "recipientAddress": "0x2455eC6700092991Ce0782365A89d5Cd89c8Fa22",
            "amount": "10",
            "amountPaid": "10",
            "status": "SUCCESS",
            "type": "WITHDRAW",
            "network": "testnet"
        }
    }
    ```
  </Tab>

  <Tab title="交换成功">
    ```json theme={null}
    {
        "event": "swap.success",
        "data": {
            "id": "99a2b490-0798-460b-9265-4d99f182fe52",
            "reference": "ZMxcorDGtf",
            "senderAddress": "0xAA2d5fd5e7bE97E214f8565DCf3a4862719960b5",
            "recipientAddress": "0xb55c054D8eE75224E1033e6eC775B4F62D942b43",
            "amount": "5",
            "status": "SUCCESS",
            "type": "SWAP",
            "toAmount": "4.965398",
            "rate": "0.9930796000000001",
            "network": "mainnet"
        }
    }
    ```
  </Tab>

  <Tab title="充值归集成功">
    ```json theme={null}
    {
        "event": "deposit.swept.success",
        "data": {
            "id": "6d2f9646-cae4-48a5-8bfe-1f9379868d4f",
            "reference": "LSk5RLfSrR",
            "amount": "10.0",
            "assetSwept": true,
            "assetSweptAt": "2024-10-23T11:28:52.848Z",
            "assetSweptGasFee": "0.000074597672027028",
            "assetSweptHash": "0xe5ae1c025b81ff83a03189fad1c0f5bd502cd0b394bed6765603f9ba05bfe147",
            "assetSweptSenderAddress": "0xe1037B45b48390285e5067424053fa35c478296b",
            "assetSweptRecipientAddress": "0x947514e4B803e312C312da0F1B41fEDdbe15ae7a",
            "assetSweptAmount": "10.0",
            "reason": "Funds swept successfully",
            "status": "SUCCESS",
            "type": "DEPOSIT",
            "network": "testnet"
        }
    }
    ```
  </Tab>

  <Tab title="提现失败">
    ```json theme={null}
    {
        "event": "withdraw.failed",
        "data": {
            "id": "081d6315-159f-4c38-b02a-c4708836c5bd",
            "reference": "y8lvy55cK",
            "senderAddress": "0x947514e4B803e312C312da0F1B41fEDdbe15ae7a",
            "recipientAddress": "0x2455eC6700092991Ce0782365A89d5Cd89c8Fa22",
            "amount": "10",
            "status": "FAILED",
            "type": "WITHDRAW",
            "network": "testnet"
        }
    }
    ```
  </Tab>
</Tabs>

<br />

<br />

祝您开发愉快！
