---
title: "Callback phê duyệt"
description: "Phê duyệt hoặc từ chối mọi thao tác của cosigner từ máy chủ của riêng bạn bằng JWT ký RS256."
url: https://docs.fystack.io/vi/cosigner/approval-callback
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.

# Callback phê duyệt

Trước khi tham gia bất kỳ phiên nào, cosigner gửi một yêu cầu có chữ ký đến máy chủ của bạn và chờ nhận một `approve` có chữ ký. Máy chủ của bạn nhận được dữ liệu byte thô cần ký, nhờ đó có thể xác minh giao dịch độc lập với Fystack.

_Diagram: Cosigner gửi yêu cầu có chữ ký đến callback, callback trả về approve hoặc reject_

## Cách hoạt động

1. Cosigner xác minh `session_start` của orchestrator.
2. Cosigner gửi `POST` một JWT RS256 đến URL của bạn (`Content-Type: application/jwt`).
3. Máy chủ của bạn xác minh JWT, chạy chính sách và trả về `200` kèm một JWT RS256.
4. Cosigner xác minh phản hồi và `request_id`, sau đó tham gia phiên.

Yêu cầu được gửi cho `KEYGEN`, `SIGN` và `RESHARE`. Hãy áp dụng chính sách giao dịch cho `SIGN`.

> **Note:** Cosigner mặc định từ chối khi có lỗi (fail closed). Từ chối, phản hồi khác 200, chuyển hướng, hết thời gian chờ, body quá lớn, chữ ký sai hoặc `request_id` sai đều có nghĩa là không ký. Phiên sẽ thất bại với `CALLBACK_REJECTED`.

## Thiết lập

Hai cặp khóa RSA-4096. Chỉ khóa công khai được trao đổi.

- **Khóa cosigner**: Được tạo bởi `cosigner-cli`, dùng để ký yêu cầu. Cung cấp `cosigner-public.pem` cho máy chủ của bạn.
- **Khóa máy chủ**: Được tạo trên máy chủ của bạn hoặc trong KMS, dùng để ký phản hồi. Cung cấp `server-public.pem` cho cosigner.

**Cùng host:** trỏ đến thư mục khóa của máy chủ. CLI sẽ đọc `server-public.pem` và ghi `cosigner-public.pem`. URL mặc định là `http://host.docker.internal:8089/cosigner/approve`.

```bash
cosigner-cli callback add ./node-callback-server/keys
```

**Máy chủ từ xa:**

```bash
cosigner-cli callback add https://risk.example.com/cosigner/approve \
  --server-public-key ./server-public.pem
```

CLI in ra đường dẫn và fingerprint của `cosigner-public.pem`; hãy cài đặt tệp này trên máy chủ của bạn. Thêm `--ca ./ca.pem` nếu dùng CA riêng.

`callback add` cập nhật `config.yaml`, đặt `require_callback: true` và khởi động lại cosigner (bỏ qua bằng `--no-restart`). `cosigner-cli info` hiển thị URL và fingerprint hiện tại.

```yaml
approval:
  require_callback: true
callback:
  url: https://risk.example.com/cosigner/approve
  timeout: 30s
  retry_count: 1
  private_key:
    type: file
    config:
      path: /run/secrets/cosigner-private.der.hex
  response_public_key_path: /run/secrets/server-public.pem
  # ca_cert_path: /run/secrets/callback-ca.pem
```

- `approval.require_callback` (boolean): Từ chối mọi phiên nếu không có callback. Giữ `true` trên production.
- `callback.url` (string): Chỉ hỗ trợ HTTPS. Cho phép HTTP thường với `host.docker.internal`. `/cosigner/approve` sẽ được nối thêm nếu không có đường dẫn.
- `callback.timeout` (duration): Tổng thời gian cho phép, bao gồm cả lần thử lại. Đồng thời là thời hạn của JWT yêu cầu. Mặc định `30s`.
- `callback.retry_count` (integer): Số lần thử lại khi gặp lỗi mạng hoặc `5xx`, dùng cùng token. Tối đa và mặc định là `1`.
- `callback.private_key` (secret provider): Khóa ký RSA-4096 của cosigner, định dạng hex PKCS#8 DER.
- `callback.response_public_key_path` (string): Khóa công khai RSA-4096 của máy chủ của bạn (PEM).
- `callback.ca_cert_path` (string): Chứng chỉ CA cho chứng chỉ máy chủ riêng hoặc tự ký.

## Yêu cầu

JWT header `{"alg": "RS256", "typ": "JWT"}`. Chỉ chấp nhận `RS256` và xác minh bằng `cosigner-public.pem` trước khi đọc các claim.

- `request_id` (string (UUID)): Duy nhất cho mỗi yêu cầu, đồng thời là `jti`. Hãy trả lại giá trị này. Dùng làm khóa idempotency.
- `iat, exp` (number): `exp` = `iat` + timeout.
- `cosigner_id, workspace_id, participant_id` (string): Định danh của cosigner.
- `session_id, orchestrator_id` (string): Phiên và orchestrator đã khởi tạo phiên đó.
- `coordinator_sequence, coordinator_signature` (number, string): Chữ ký orchestrator đã được xác minh. Ghi log để truy vết.
- `protocol` (string): `ECDSA` hoặc `EdDSA`.
- `operation` (string): `KEYGEN`, `SIGN`, `RESHARE` hoặc `PING`.
- `threshold, participants` (number, array): `participant_id`, `party_key`, `identity_public_key` của từng thành viên.
- `key_id` (string): Khóa ví.
- `signing_input` (string (base64)): Chỉ với `SIGN`. Chính xác dữ liệu byte cần ký.
- `signing_input_sha256` (string (hex)): Chỉ với `SIGN`. SHA-256 của `signing_input`.
- `signing_context` (object): Chỉ với `SIGN`, tùy chọn. Chi tiết giao dịch từ Fystack, tối đa 64 KiB.
- `derivation` (object): Chỉ với `SIGN`, tùy chọn. `path` và `delta` ECDSA không hardened.
- `new_threshold, new_participants` (number, array): Chỉ với `RESHARE`.

```json
{
  "request_id": "3f1c2a8e-7b0d-4d0c-9a51-2f6c4f0e9b12",
  "operation": "SIGN",
  "protocol": "ECDSA",
  "key_id": "8b0f3c4e-...",
  "signing_input": "q83vEjRWeJA...",
  "signing_input_sha256": "5d41402abc4b2a76...",
  "signing_context": {
    "version": 1,
    "apex_request_id": "wd_01J9...",
    "reference": { "type": "onchain_tx", "id": "..." },
    "requested_at": "2026-10-12T08:30:00Z",
    "chain": { "network": "ethereum", "chain_id": "1", "protocol": "ECDSA" },
    "wallet": { "wallet_id": "8b0f3c4e-...", "derivation_path": [44, 60, 0, 0, 0] },
    "tx": {
      "raw": "0x02f8...",
      "payload_hash": "0x5d41402a...",
      "verified": true,
      "verification_mode": "evm_transaction",
      "decoded": { "to_address": "0xAbC...", "value": "1000000000000000000" }
    }
  },
  "threshold": 1,
  "participants": [ ... ]
}
```

`verification_mode`: `evm_transaction`, `solana_message`, `tron_transaction`, `bitcoin_transaction` hoặc `digest_only`.

> **Note:** `signing_context` chỉ là thông tin gợi ý, không phải bằng chứng. Cosigner không đối chiếu nó với `signing_input`. Hãy tự giải mã `tx.raw`, tính lại mã băm, so sánh với `signing_input`, sau đó kiểm tra địa chỉ đích, số tiền và rủi ro. Từ chối những gì bạn không thể xác minh.

## Phản hồi

HTTP `200`, `Content-Type: application/jwt`, ký RS256 bằng khóa máy chủ của bạn. Tối đa 64 KiB. Chuyển hướng sẽ không được theo.

- `request_id` (string): Phải khớp với yêu cầu.
- `action` (string): `approve` hoặc `reject`. `pong` cho `PING`.
- `reason` (string): Bắt buộc khi `reject`, tối đa 1024 byte, bỏ qua khi `approve`. Được ghi vào nhật ký kiểm toán.
- `exp` (number, required): Nên đặt ngắn, ví dụ 60 giây.

```json
{
  "request_id": "3f1c2a8e-7b0d-4d0c-9a51-2f6c4f0e9b12",
  "action": "reject",
  "reason": "destination_not_allowlisted",
  "iat": 1791794400,
  "exp": 1791794460,
  "jti": "b7e5..."
}
```

## Kiểm tra hoạt động

Cosigner gửi `"operation": "PING"` khi khởi động và cứ mỗi 30 giây. Hãy trả lời `"action": "pong"` mà không cần chạy chính sách.

- Ping khi khởi động thất bại: cosigner dừng lại.
- Các lần ping sau thất bại: trạng thái sức khỏe hiển thị `callback_unavailable` và mọi yêu cầu ký bị từ chối cho đến khi callback phục hồi.

## Máy chủ mẫu

`cosigner-cli` đi kèm một ví dụ Node.js trong `examples/node-callback-server` (`express` + `jose`).

```js
app.post('/cosigner/approve', express.text({ type: 'application/jwt' }), async (req, res) => {
  try {
    // RS256 only, signed by the pinned cosigner key, with exp and request_id.
    const request = await verifier.verify(req.body.trim())

    if (request.operation === 'PING') {
      return res.type('application/jwt').send(await signer.sign(request, { action: 'pong' }))
    }

    let decision = { action: 'approve' }
    if (request.operation === 'SIGN' && !(await policyAllows(request))) {
      decision = { action: 'reject', reason: 'destination_not_allowlisted' }
    }

    res.type('application/jwt').send(await signer.sign(request, decision))
  } catch (err) {
    res.sendStatus(401)
  }
})
```

> **Note:** Ở trạng thái mặc định, ví dụ này phê duyệt mọi thứ. Hãy thay thế logic quyết định trước khi dùng tiền thật.

## Danh sách kiểm tra cho production

- Giữ `require_callback: true`. `cosigner-cli callback remove` sẽ tắt hoàn toàn cơ chế phê duyệt.
- Mặc định từ chối.
- Giải mã `tx.raw` và đối chiếu với `signing_input`.
- Áp dụng danh sách cho phép, hạn mức và kiểm tra AML.
- Đảm bảo idempotent theo `request_id`; các lần thử lại sẽ gửi lại cùng token.
- Lưu khóa máy chủ trong KMS hoặc HSM. AWS KMS `RSA_4096` với `RSASSA_PKCS1_V1_5_SHA_256` hoạt động với RS256.
- Phản hồi sớm, trong giới hạn thời gian chờ.
- Thiết lập cảnh báo khi có `callback_unavailable` và khi có yêu cầu bị từ chối.
