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
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
- Name
- success
- Type
- boolean
- Description
Cho biết yêu cầu có thành công hay không.
- Name
- message
- Type
- string
- Description
Thông báo mô tả kết quả của thao tác.
- Name
- code
- Type
- integer
- Description
Mã số cho biết trạng thái kết quả (0 nghĩa là thành công).
- Name
- data
- Type
- object
- Description
Chứa thông tin chi tiết của khóa công khai.
- Name
- public_key
- Type
- string
- Description
Khóa công khai Ed25519 dùng để xác minh chữ ký webhook.
Hãy lưu khóa công khai này, bạn sẽ cần nó để xác minh chữ ký webhook.
Request
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")
Response
200{
"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
- Name
- deposit.pending
- Type
- Description
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.
- Name
- deposit.confirmed
- Type
- Description
Một giao dịch nạp tiền đã được xác nhận trên blockchain.
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 bên dưới.
Sự kiện rút tiền
- Name
- withdrawal.pending
- Type
- Description
Một yêu cầu rút tiền đang chờ phê duyệt.
- Name
- withdrawal.executed
- Type
- Description
Giao dịch rút tiền đã được gửi lên blockchain.
- Name
- withdrawal.confirmed
- Type
- Description
Giao dịch rút tiền đã được xác nhận trên blockchain.
- Name
- withdrawal.failed
- Type
- Description
Giao dịch rút tiền đã thất bại. Payload là đối tượng withdrawal với
status: "FAILED". Đối tượngtransactionlồng bên trong là tùy chọn và có thể lànull.
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
- Name
- x-webhook-signature
- Type
- string
- Description
Chữ ký Ed25519 của payload JSON đã được chuẩn hóa.
- Name
- x-webhook-event
- Type
- string
- Description
Loại sự kiện đã kích hoạt webhook này.
Payload của webhook
- Name
- webhook_id
- Type
- string
- Description
UUID của cấu hình webhook.
- Name
- resource_id
- Type
- string
- Description
UUID của tài nguyên đã kích hoạt webhook.
- Name
- url
- Type
- string
- Description
URL mà webhook đã được gửi đến.
- Name
- event
- Type
- string
- Description
Loại sự kiện (ví dụ:
deposit.confirmed).
- Name
- payload
- Type
- object
- Description
Payload dữ liệu riêng của sự kiện.
Idempotency Key
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.
- Name
- idempotency_key
- Type
- string
- Description
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_keycủa webhook đến đã từng xuất hiện.
Xác minh chữ ký
Để xác minh chữ ký webhook, bạn cần:
- Chuẩn hóa payload JSON (đảm bảo thứ tự các khóa của đối tượng nhất quán)
- Xác minh chữ ký Ed25519 bằng khóa công khai của bạn
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.
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 })
})
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
{
"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"
}
Đă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.