---
title: "Webhook"
description: "Tài liệu tham khảo API cho hệ thống webhook của Fystack."
url: https://docs.fystack.io/vi/webhooks
last_updated: 2026-11-13
---

> ## Documentation index
> Fetch the complete documentation index at: https://docs.fystack.io/llms.txt
> Use it to discover all available pages before exploring further.

# Webhook

Webhook cho phép bạn nhận thông báo theo thời gian thực khi các sự kiện cụ thể xảy ra trong tài khoản Fystack của bạn.

---

## Lấy khóa công khai của webhook

**Endpoint:** `GET /workspaces/:workspace_id/webhook-verification-key`

Truy xuất khóa công khai dùng để xác minh chữ ký webhook cho workspace của bạn. Mỗi yêu cầu webhook đều được ký bằng Ed25519, và bạn nên xác minh chữ ký này trước khi xử lý dữ liệu webhook.

### Phản hồi

- `success` (boolean): Cho biết yêu cầu có thành công hay không.
- `message` (string): Thông báo mô tả kết quả của thao tác.
- `code` (integer): Mã số cho biết trạng thái kết quả (0 nghĩa là thành công).
- `data` (object): Chứa thông tin chi tiết của khóa công khai.
  - `public_key` (string): Khóa công khai Ed25519 dùng để xác minh chữ ký webhook.

> **Warning.** Hãy lưu khóa công khai này, bạn sẽ cần nó để xác minh chữ ký webhook.

**Request** `GET /workspaces/:workspace_id/webhook-verification-key`

```typescript
import { FystackSDK, Environment } from "@fystack/sdk"

const apiCredentials = {
  apiKey: process.env.API_KEY,
  apiSecret: process.env.API_SECRET
}

const sdk = new FystackSDK({
  credentials: apiCredentials,
  environment: Environment.Sandbox,
  logger: true
})

const response = await sdk.getWebhookPublicKey(
  "workspace_id"
)
```

```bash
curl -X GET https://api.fystack.io/api/v1/workspaces/workspace_id/webhook-verification-key \
  -H "ACCESS-API-KEY: your_api_key" \
  -H "ACCESS-TIMESTAMP: 1667836889" \
  -H "ACCESS-SIGN: YourBase64EncodedSignature=="
```

```json
{
  "success": true,
  "message": "success",
  "code": 0,
  "data": {
    "public_key": "503d18d8375e60667a4cdb879e72c249b3dc054e7d6443c5ccd93aef7f6547fb"
  }
}
```

---

## Sự kiện webhook

Fystack gửi thông báo webhook cho nhiều sự kiện khác nhau. Mỗi loại sự kiện đại diện cho một thay đổi trạng thái cụ thể trong tài khoản của bạn.

### Sự kiện nạp tiền

- `deposit.pending`: Một giao dịch nạp tiền mới đã được phát hiện nhưng chưa được xác nhận trên blockchain.
- `deposit.confirmed`: Một giao dịch nạp tiền đã được xác nhận trên blockchain.

> **Warning.** Payload của sự kiện nạp tiền bao gồm trường `idempotency_key`. Hãy sử dụng nó để loại bỏ trùng lặp các lần gửi webhook một cách an toàn, xem [Idempotency Key](#idempotency-key) bên dưới.

### Sự kiện rút tiền

- `withdrawal.pending`: Một yêu cầu rút tiền đang chờ phê duyệt.
- `withdrawal.executed`: Giao dịch rút tiền đã được gửi lên blockchain.
- `withdrawal.confirmed`: Giao dịch rút tiền đã được xác nhận trên blockchain.
- `withdrawal.failed`: Giao dịch rút tiền đã thất bại. Payload là đối tượng withdrawal với `status: "FAILED"`. Đối tượng `transaction` lồng bên trong là tùy chọn và có thể là `null`.

> **Info.** Khi một giao dịch rút tiền thất bại, số tiền bị tạm giữ sẽ được hoàn lại vào số dư ví. Bạn có thể tạo giao dịch rút tiền mới một cách an toàn.

---

## Xác minh webhook

Mỗi yêu cầu webhook đều bao gồm một chữ ký trong header `x-webhook-signature`. Bạn nên xác minh chữ ký này để đảm bảo webhook đến từ Fystack.

### Header

- `x-webhook-signature` (string): Chữ ký Ed25519 của payload JSON đã được chuẩn hóa.
- `x-webhook-event` (string): Loại sự kiện đã kích hoạt webhook này.

### Payload của webhook

- `webhook_id` (string): UUID của cấu hình webhook.
- `resource_id` (string): UUID của tài nguyên đã kích hoạt webhook.
- `url` (string): URL mà webhook đã được gửi đến.
- `event` (string): Loại sự kiện (ví dụ: `deposit.confirmed`).
- `payload` (object): Payload dữ liệu riêng của sự kiện.

### Idempotency Key

> **Warning.** Webhook nạp tiền (`deposit.pending` và `deposit.confirmed`) có thể được gửi nhiều lần cho cùng một giao dịch nạp tiền, ví dụ sau khi thử lại do hết thời gian chờ hoặc do endpoint của bạn tạm thời gặp sự cố. Luôn sử dụng `idempotency_key` để loại bỏ xử lý trùng lặp thay vì chỉ dựa vào `resource_id` hoặc `id`.

Đối với sự kiện nạp tiền, trường `payload.idempotency_key` định danh duy nhất một sự kiện nạp tiền gốc. Vì điều kiện mạng hoặc thời gian ngừng hoạt động của endpoint có thể khiến Fystack gửi lại cùng một webhook, bạn nên coi hai lần gửi bất kỳ có cùng `idempotency_key` là cùng một sự kiện và chỉ xử lý một lần.

- `idempotency_key` (string): Khóa duy nhất định danh sự kiện nạp tiền này. Hãy lưu các khóa bạn đã xử lý (ví dụ: trong cơ sở dữ liệu có ràng buộc unique) và bỏ qua việc xử lý nếu `idempotency_key` của webhook đến đã từng xuất hiện.

### Xác minh chữ ký

Để xác minh chữ ký webhook, bạn cần:

1. Chuẩn hóa payload JSON (đảm bảo thứ tự các khóa của đối tượng nhất quán)
2. Xác minh chữ ký Ed25519 bằng khóa công khai của bạn

> **Warning.** Luôn chuẩn hóa payload sự kiện trước khi xác minh. Các bộ tuần tự hóa JSON khác nhau có thể tạo ra các chuỗi biểu diễn khác nhau cho cùng một cấu trúc dữ liệu.

```typescript
import * as ed from '@noble/ed25519'

// Example public key from Fystack
const FYSTACK_PUBLIC_KEY = '503d18d8375e60667a4cdb879e72c249b3dc054e7d6443c5ccd93aef7f6547fb'

// Canonicalize JSON by sorting keys recursively
function canonicalizeJSON(inputObject: Record<string, any>): string {
  if (typeof inputObject !== 'object' || inputObject === null) {
    throw new Error('Input must be a non-null object.')
  }

  const sortKeys = (value: any): any => {
    if (Array.isArray(value)) {
      return value.map(sortKeys)
    }
    if (value && typeof value === 'object' && value.constructor === Object) {
      return Object.keys(value)
        .sort()
        .reduce((sortedObj: Record<string, any>, key: string) => {
          sortedObj[key] = sortKeys(value[key])
          return sortedObj
        }, {})
    }
    return value
  }

  return JSON.stringify(sortKeys(inputObject))
}

// Verify Ed25519 signature
async function verifyWebhook(payload: object, signatureHex: string): Promise<boolean> {
  const canonical = canonicalizeJSON(payload)
  const msg = new TextEncoder().encode(canonical)
  return ed.verify(signatureHex, msg, FYSTACK_PUBLIC_KEY)
}

// Example usage
app.post('/webhook', async (req, res) => {
  const signature = req.headers['x-webhook-signature']
  const event = req.headers['x-webhook-event']
  const payload = req.body

  // Verify signature using your stored public key
  const isValid = await verifyWebhook(
    payload,
    signature
  )

  if (!isValid) {
    return res.status(400).json({ error: 'Invalid signature' })
  }

  // Handle different event types
  switch (event) {
    case 'deposit.confirmed':
      // Handle confirmed deposit
      break
    case 'withdrawal.confirmed':
      // Handle confirmed withdrawal
      break
    // ... handle other events
  }

  res.json({ received: true })
})
```

```go
package main

import (
  "crypto/ed25519"
  "encoding/hex"
  "encoding/json"
  "fmt"
)

// Example public key from Fystack
const FystackPublicKey = "503d18d8375e60667a4cdb879e72c249b3dc054e7d6443c5ccd93aef7f6547fb"

// CanonicalizeJSON ensures consistent JSON representation
func CanonicalizeJSON(data []byte) ([]byte, error) {
  var temp map[string]interface{}
  err := json.Unmarshal(data, &temp)
  if err != nil {
    return nil, fmt.Errorf("invalid JSON: %w", err)
  }

  canonicalJSON, err := json.Marshal(temp)
  if err != nil {
    return nil, fmt.Errorf("failed to marshal JSON: %w", err)
  }
  return canonicalJSON, nil
}

// VerifyWebhook verifies the webhook signature
func VerifyWebhook(payload []byte, signatureHex string) (bool, error) {
  // Decode hex strings
  publicKey, err := hex.DecodeString(FystackPublicKey)
  if err != nil {
    return false, fmt.Errorf("invalid public key: %w", err)
  }

  signature, err := hex.DecodeString(signatureHex)
  if err != nil {
    return false, fmt.Errorf("invalid signature: %w", err)
  }

  // Canonicalize payload
  canonical, err := CanonicalizeJSON(payload)
  if err != nil {
    return false, fmt.Errorf("failed to canonicalize JSON: %w", err)
  }

  // Verify signature
  return ed25519.Verify(publicKey, canonical, signature), nil
}

// Example usage in HTTP handler
func handleWebhook(w http.ResponseWriter, r *http.Request) {
  signature := r.Header.Get("x-webhook-signature")
  event := r.Header.Get("x-webhook-event")

  body, err := io.ReadAll(r.Body)
  if err != nil {
    http.Error(w, "Failed to read body", http.StatusBadRequest)
    return
  }

  isValid, err := VerifyWebhook(
    body,
    signature,
  )
  if err != nil || !isValid {
    http.Error(w, "Invalid signature", http.StatusBadRequest)
    return
  }

  // Handle webhook...
}
```

```php
<?php

// Example public key from Fystack
const FYSTACK_PUBLIC_KEY = '503d18d8375e60667a4cdb879e72c249b3dc054e7d6443c5ccd93aef7f6547fb';

/**
 * Canonicalize JSON by decoding and re-encoding with sorted keys
 */
function canonicalizeJSON($data) {
    $decoded = json_decode($data, true);
    if (json_last_error() !== JSON_ERROR_NONE) {
        throw new Exception('Invalid JSON: ' . json_last_error_msg());
    }

    // Sort all arrays and objects recursively
    array_walk_recursive($decoded, function(&$item, $key) {
        if (is_array($item)) {
            ksort($item);
        }
    });
    ksort($decoded);

    return json_encode($decoded);
}

/**
 * Verify webhook signature using Ed25519
 */
function verifyWebhook($payload, $signatureHex) {
    try {
        // Convert hex to binary
        $publicKey = hex2bin(FYSTACK_PUBLIC_KEY);
        $signature = hex2bin($signatureHex);

        // Canonicalize payload
        $canonical = canonicalizeJSON($payload);

        // Verify using sodium
        return sodium_crypto_sign_verify_detached(
            $signature,
            $canonical,
            $publicKey
        );
    } catch (Exception $e) {
        error_log('Webhook verification failed: ' . $e->getMessage());
        return false;
    }
}

// Example usage in a webhook handler
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$event = $_SERVER['HTTP_X_WEBHOOK_EVENT'] ?? '';
$payload = file_get_contents('php://input');

if (!verifyWebhook(
    $payload,
    $signature
)) {
    http_response_code(400);
    echo json_encode(['error' => 'Invalid signature']);
    exit;
}

// Handle different event types
switch ($event) {
    case 'deposit.confirmed':
        // Handle confirmed deposit
        break;
    case 'withdrawal.confirmed':
        // Handle confirmed withdrawal
        break;
}

echo json_encode(['received' => true]);
```

```python
import json
import ed25519
from typing import Dict, Any
from binascii import unhexlify

# Example public key from Fystack
FYSTACK_PUBLIC_KEY = '503d18d8375e60667a4cdb879e72c249b3dc054e7d6443c5ccd93aef7f6547fb'

def canonicalize_json(data: Dict[Any, Any]) -> str:
    """Ensure consistent JSON representation."""
    return json.dumps(data, sort_keys=True, separators=(',', ':'))

def verify_webhook(payload: Dict[Any, Any], signature_hex: str) -> bool:
    """Verify webhook signature."""
    try:
        # Convert hex strings to bytes
        public_key = unhexlify(FYSTACK_PUBLIC_KEY)
        signature = unhexlify(signature_hex)

        # Canonicalize payload
        canonical = canonicalize_json(payload).encode('utf-8')

        # Create verifier
        verifier = ed25519.VerifyingKey(public_key)
        return verifier.verify(signature, canonical)
    except Exception as e:
        print(f"Verification failed: {e}")
        return False

# Example Flask route
@app.route('/webhook', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('x-webhook-signature')
    event = request.headers.get('x-webhook-event')
    payload = request.json

    if not verify_webhook(
        payload,
        signature
    ):
        return jsonify({'error': 'Invalid signature'}), 400

    # Handle webhook...
    return jsonify({'received': True})
```

```java
import com.fasterxml.jackson.databind.ObjectMapper;
import org.bouncycastle.crypto.signers.Ed25519Signer;
import org.bouncycastle.util.encoders.Hex;

public class WebhookVerifier {
    private static final ObjectMapper MAPPER = new ObjectMapper();

    // Example public key from Fystack
    private static final String FYSTACK_PUBLIC_KEY = "503d18d8375e60667a4cdb879e72c249b3dc054e7d6443c5ccd93aef7f6547fb";

    public static String canonicalizeJSON(Object data) throws JsonProcessingException {
        // Use Jackson's sorted map feature for consistent ordering
        return MAPPER.writer().writeValueAsString(data);
    }

    public static boolean verifyWebhook(Object payload, String signatureHex) {
        try {
            byte[] publicKey = Hex.decode(FYSTACK_PUBLIC_KEY);
            byte[] signature = Hex.decode(signatureHex);

            // Canonicalize payload
            String canonical = canonicalizeJSON(payload);
            byte[] message = canonical.getBytes(StandardCharsets.UTF_8);

            Ed25519Signer verifier = new Ed25519Signer();
            verifier.init(false, new Ed25519PublicKeyParameters(publicKey, 0));
            verifier.update(message, 0, message.length);

            return verifier.verifySignature(signature);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    // Example Spring controller
    @PostMapping("/webhook")
    public ResponseEntity<?> handleWebhook(
        @RequestHeader("x-webhook-signature") String signature,
        @RequestHeader("x-webhook-event") String event,
        @RequestBody Map<String, Object> payload
    ) {
        if (!verifyWebhook(
            payload,
            signature
        )) {
            return ResponseEntity.badRequest().body("Invalid signature");
        }

        // Handle webhook...
        return ResponseEntity.ok().build();
    }
}
```

> **Info.** Bước chuẩn hóa là rất quan trọng: nó đảm bảo cùng một dữ liệu JSON sẽ luôn tạo ra cùng một chuỗi biểu diễn, bất kể ban đầu nó được định dạng hay sắp xếp như thế nào.

### Ví dụ payload sự kiện

**Event Examples**

```json
{
  "webhook_id": "73893532-8e3d-4bc7-b3f9-148c9b82b811",
  "resource_id": "62ef8383-e897-449f-b9d8-78fffaa26a61",
  "url": "https://webhook.site/56e92433-823d-4b28-9cc9-9298076af671",
  "payload": {
    "amount": "1000",
    "asset": {
      "address": "0xDD75BEb521f4513D65fFbbE91ab7131f3cD92080",
      "address_type": "evm",
      "created_at": "2024-04-12T22:58:36.133934+07:00",
      "decimals": 6,
      "id": "f1be472d-1338-43ff-b547-06faba7ecce7",
      "is_native": false,
      "is_whitelisted": true,
      "logo_url": "https://assets-cdn.trustwallet.com/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
      "name": "USDC Sepolia test",
      "network_id": "23d13c03-1f3c-4e2a-9aef-1771a244b7f6",
      "symbol": "USDC",
      "updated_at": "2024-04-12T22:58:36.133934+07:00"
    },
    "asset_hold": null,
    "asset_hold_id": null,
    "asset_id": "f1be472d-1338-43ff-b547-06faba7ecce7",
    "block_number": 8960514,
    "created_at": "2025-08-11T17:31:24+07:00",
    "description": "",
    "direction": "in",
    "from_address": "0xe6EBF81E9C225BbCEa9b5399E0D0d0f29f30f119",
    "id": "62ef8383-e897-449f-b9d8-78fffaa26a61",
    "idempotency_key": "bfa74b5d5b591794031e0f78a556c80b7b89875378baac4df75b8302cf80b72c",
    "method": "transfer",
    "network": null,
    "network_id": "23d13c03-1f3c-4e2a-9aef-1771a244b7f6",
    "price_native_token": "4260.480000022453",
    "price_token": "0.9997516371944777",
    "status": "pending",
    "to_address": "0xA118fa3d039BA6ad3BB0bA8ac8F243F727B2F99F",
    "tx_fee": "0.000051521446618",
    "tx_hash": "0x21c201ff8e02abdcd4395c71639266d953cb18cb159bdcd28b7556c482b4881a",
    "type": "token_transfer",
    "updated_at": "2025-08-11T17:31:26.815540431+07:00",
    "user": null,
    "user_id": "075243c1-962e-4803-83da-d73c78b8b060",
    "wallet": {
      "created_at": "2025-08-11T10:35:54.174577+07:00",
      "deleted_at": null,
      "disabled": false,
      "id": "e0851607-417a-475a-b399-5f3aa262b81d",
      "name": "hello1",
      "threshold": 1,
      "updated_at": "2025-08-11T10:35:54.174577+07:00",
      "user_id": "075243c1-962e-4803-83da-d73c78b8b060",
      "wallet_purpose": "general",
      "wallet_type": "standard",
      "workspace_id": "6bcfe6c7-281b-4773-a188-e78f5cad3336"
    },
    "wallet_id": "e0851607-417a-475a-b399-5f3aa262b81d",
    "workspace": null,
    "workspace_id": "6bcfe6c7-281b-4773-a188-e78f5cad3336"
  },
  "event": "deposit.pending"
}
```

```json
{
  "webhook_id": "73893532-8e3d-4bc7-b3f9-148c9b82b811",
  "resource_id": "c6702df0-b8ef-4a38-882d-dabf0d56127d",
  "url": "https://webhook.site/56e92433-823d-4b28-9cc9-9298076af671",
  "event": "deposit.confirmed",
  "payload": {
    "id": "c6702df0-b8ef-4a38-882d-dabf0d56127d",
    "idempotency_key": "a1c92e7f4d3b8619524fbd0b3e9c15a6ed172b8d31f645a998e2c07b1a5f6d3e",
    "amount": "1000",
    "status": "confirmed",
    "tx_hash": "0x75789cbbe4d55328e6be7b7155b4028ea8afa384ffe13a1146d8bdb3eed6ba22",
    "from_address": "0xe6EBF81E9C225BbCEa9b5399E0D0d0f29f30f119",
    "to_address": "0xA793457d84CFD255Ed68295984300C4f799E759F",
    "asset_id": "f1be472d-1338-43ff-b547-06faba7ecce7",
    "wallet_id": "e0851607-417a-475a-b399-5f3aa262b81d",
    "block_number": 8960538,
    "type": "token_transfer",
    "direction": "in"
  }
}
```

```json
{
  "webhook_id": "73893532-8e3d-4bc7-b3f9-148c9b82b811",
  "resource_id": "147d4808-2e98-4a86-9ead-c227e7e80cf6",
  "url": "https://webhook.site/56e92433-823d-4b28-9cc9-9298076af671",
  "event": "withdrawal.pending",
  "payload": {
    "id": "0de79230-a820-4bd4-b020-883d395faa87",
    "amount": "1000",
    "status": "PENDING_APPROVAL",
    "recipient_address": "0x7c2BbE2e02af09F1cf9aFd4A79EC201DC841675a",
    "asset_id": "f1be472d-1338-43ff-b547-06faba7ecce7",
    "wallet_id": "e0851607-417a-475a-b399-5f3aa262b81d",
    "creator_id": "075243c1-962e-4803-83da-d73c78b8b060",
    "asset_hold_id": "2595ac0b-deb6-4c58-8466-290a248e5d2f",
    "created_at": "2025-08-11T17:37:29.815148+07:00",
    "updated_at": "2025-08-11T17:37:29.815148+07:00"
  }
}
```

```json
{
  "webhook_id": "73893532-8e3d-4bc7-b3f9-148c9b82b811",
  "resource_id": "0de79230-a820-4bd4-b020-883d395faa87",
  "url": "https://webhook.site/56e92433-823d-4b28-9cc9-9298076af671",
  "payload": {
    "amount": "1000",
    "asset": null,
    "asset_hold_id": "2595ac0b-deb6-4c58-8466-290a248e5d2f",
    "asset_id": "f1be472d-1338-43ff-b547-06faba7ecce7",
    "created_at": "2025-08-11T17:37:29.815148+07:00",
    "creator": null,
    "creator_id": "075243c1-962e-4803-83da-d73c78b8b060",
    "error_reason": null,
    "id": "0de79230-a820-4bd4-b020-883d395faa87",
    "recipient_address": "0x7c2BbE2e02af09F1cf9aFd4A79EC201DC841675a",
    "status": "EXECUTED",
    "transaction": {
      "amount": "1000",
      "asset": null,
      "asset_hold": null,
      "asset_hold_id": "2595ac0b-deb6-4c58-8466-290a248e5d2f",
      "asset_id": "f1be472d-1338-43ff-b547-06faba7ecce7",
      "block_number": 8960546,
      "created_at": "2025-08-11T17:37:45.828992+07:00",
      "description": "",
      "direction": "out",
      "from_address": "0xA118fa3d039BA6ad3BB0bA8ac8F243F727B2F99F",
      "id": "147d4808-2e98-4a86-9ead-c227e7e80cf6",
      "method": "transfer",
      "network": null,
      "network_id": "23d13c03-1f3c-4e2a-9aef-1771a244b7f6",
      "price_native_token": "4260.480000022453",
      "price_token": "0.9997516371944777",
      "status": "completed",
      "to_address": "0x7c2BbE2e02af09F1cf9aFd4A79EC201DC841675a",
      "tx_fee": "0.0000001851509817",
      "tx_hash": "0x619dea0c5eb5206a35ea22113d6b4d9e5f0ce226ae3b2ca0089ec9c341b3a210",
      "type": "withdrawal",
      "updated_at": "2025-08-11T17:37:50.765281+07:00",
      "user": null,
      "user_id": "075243c1-962e-4803-83da-d73c78b8b060",
      "wallet": null,
      "wallet_id": "e0851607-417a-475a-b399-5f3aa262b81d",
      "workspace": null,
      "workspace_id": "6bcfe6c7-281b-4773-a188-e78f5cad3336"
    },
    "transaction_id": "147d4808-2e98-4a86-9ead-c227e7e80cf6",
    "updated_at": "2025-08-11T17:37:46.069818+07:00",
    "wallet_id": "e0851607-417a-475a-b399-5f3aa262b81d",
    "withdrawal_approvals": null
  },
  "event": "withdrawal.executed"
}
```

```json
{
  "webhook_id": "73893532-8e3d-4bc7-b3f9-148c9b82b811",
  "resource_id": "147d4808-2e98-4a86-9ead-c227e7e80cf6",
  "url": "https://webhook.site/56e92433-823d-4b28-9cc9-9298076af671",
  "payload": {
    "amount": "1000",
    "asset": null,
    "asset_hold_id": "2595ac0b-deb6-4c58-8466-290a248e5d2f",
    "asset_id": "f1be472d-1338-43ff-b547-06faba7ecce7",
    "created_at": "2025-08-11T17:37:29.815148+07:00",
    "creator": null,
    "creator_id": "075243c1-962e-4803-83da-d73c78b8b060",
    "error_reason": null,
    "id": "0de79230-a820-4bd4-b020-883d395faa87",
    "recipient_address": "0x7c2BbE2e02af09F1cf9aFd4A79EC201DC841675a",
    "status": "SUCCESS",
    "transaction": {
      "amount": "1000",
      "asset": null,
      "asset_hold": null,
      "asset_hold_id": "2595ac0b-deb6-4c58-8466-290a248e5d2f",
      "asset_id": "f1be472d-1338-43ff-b547-06faba7ecce7",
      "block_number": 8960546,
      "created_at": "2025-08-11T17:37:45.828992+07:00",
      "description": "",
      "direction": "out",
      "from_address": "0xA118fa3d039BA6ad3BB0bA8ac8F243F727B2F99F",
      "id": "147d4808-2e98-4a86-9ead-c227e7e80cf6",
      "method": "transfer",
      "network": null,
      "network_id": "23d13c03-1f3c-4e2a-9aef-1771a244b7f6",
      "price_native_token": "4260.480000022453",
      "price_token": "0.9997516371944777",
      "status": "confirmed",
      "to_address": "0x7c2BbE2e02af09F1cf9aFd4A79EC201DC841675a",
      "tx_fee": "0.0000001851509817",
      "tx_hash": "0x619dea0c5eb5206a35ea22113d6b4d9e5f0ce226ae3b2ca0089ec9c341b3a210",
      "type": "withdrawal",
      "updated_at": "2025-08-11T17:38:05.607714+07:00",
      "user": null,
      "user_id": "075243c1-962e-4803-83da-d73c78b8b060",
      "wallet": null,
      "wallet_id": "e0851607-417a-475a-b399-5f3aa262b81d",
      "workspace": null,
      "workspace_id": "6bcfe6c7-281b-4773-a188-e78f5cad3336"
    },
    "transaction_id": "147d4808-2e98-4a86-9ead-c227e7e80cf6",
    "updated_at": "2025-08-11T17:38:06.057452+07:00",
    "wallet_id": "e0851607-417a-475a-b399-5f3aa262b81d",
    "withdrawal_approvals": null
  },
  "event": "withdrawal.confirmed"
}
```

```json
{
  "webhook_id": "73893532-8e3d-4bc7-b3f9-148c9b82b811",
  "resource_id": "3e8a6d21-7f4c-4b19-a0d5-2c9e1b7f8a36",
  "url": "https://webhook.site/56e92433-823d-4b28-9cc9-9298076af671",
  "payload": {
    "id": "3e8a6d21-7f4c-4b19-a0d5-2c9e1b7f8a36",
    "amount": "1000",
    "status": "FAILED",
    "recipient_address": "0x7c2BbE2e02af09F1cf9aFd4A79EC201DC841675a",
    "asset": null,
    "asset_id": "f1be472d-1338-43ff-b547-06faba7ecce7",
    "asset_hold_id": "8f4b2a90-1c6d-4e57-b3a8-0d9e6f1c2b75",
    "wallet_id": "e0851607-417a-475a-b399-5f3aa262b81d",
    "creator": null,
    "creator_id": "075243c1-962e-4803-83da-d73c78b8b060",
    "error_reason": null,
    "fee_priority": "standard",
    "approval_threshold": 0,
    "withdrawal_approvals": null,
    "transaction_id": "d47c1e93-5a2b-4f80-9e6d-1b3a8c7f0e52",
    "transaction": {
      "amount": "1000",
      "asset": null,
      "asset_hold": null,
      "asset_hold_id": "8f4b2a90-1c6d-4e57-b3a8-0d9e6f1c2b75",
      "asset_id": "f1be472d-1338-43ff-b547-06faba7ecce7",
      "block_number": 0,
      "created_at": "2025-08-11T18:10:02.118254+07:00",
      "description": "",
      "direction": "out",
      "from_address": "0xA118fa3d039BA6ad3BB0bA8ac8F243F727B2F99F",
      "id": "d47c1e93-5a2b-4f80-9e6d-1b3a8c7f0e52",
      "method": "transfer",
      "network": null,
      "network_id": "23d13c03-1f3c-4e2a-9aef-1771a244b7f6",
      "failed_reason": "Signing timeout",
      "price_native_token": "0",
      "price_token": "0",
      "status": "failed",
      "to_address": "0x7c2BbE2e02af09F1cf9aFd4A79EC201DC841675a",
      "tx_fee": "0",
      "tx_hash": null,
      "type": "withdrawal",
      "updated_at": "2025-08-11T18:15:02.540917+07:00",
      "user": null,
      "user_id": "075243c1-962e-4803-83da-d73c78b8b060",
      "wallet": null,
      "wallet_id": "e0851607-417a-475a-b399-5f3aa262b81d",
      "workspace": null,
      "workspace_id": "6bcfe6c7-281b-4773-a188-e78f5cad3336"
    },
    "created_at": "2025-08-11T18:09:58.774102+07:00",
    "updated_at": "2025-08-11T18:15:02.611385+07:00"
  },
  "event": "withdrawal.failed"
}
```

> **Info.** Đăng ký các endpoint webhook của bạn trên cổng Fystack và chọn những sự kiện bạn muốn nhận.
