Skip to content
Translated page. The English version is the source of truth.

Webhook — thông báo đơn hàng ​

Đăng ký một endpoint HTTPS để nhận một webhook có chữ ký ngay thời điểm một trong các đơn hàng của bạn được hoàn thành và xác minh on-chain. Thay vì liên tục gửi yêu cầu kiểm tra (polling), bạn tiếp tục luồng xử lý của mình (ví dụ: giải phóng USDT) ngay khi thông báo đến.

Ba sự kiện được gửi đi:

Sự kiệnĐược gửi khi
delegation.confirmedMột lượt thuê energy (1h / 5m) được xác nhận on-chain
bandwidth.delegatedMột đơn hàng bandwidth được hoàn thành
activation.confirmedMột lượt kích hoạt địa chỉ được thực thi on-chain

Trang này trình bày về API quản trị (tạo / liệt kê / chỉnh sửa / xoay vòng secret / xóa các endpoint của bạn) và định dạng của các webhook mà chúng tôi gửi đến bạn.

ℹ️ Vai trò. Bạn quản lý các endpoint của mình tại đây. Việc phân phối được Netts thực hiện bất đồng bộ sau khi đơn hàng được xác minh — không cần phải poll. Chỉ các sự kiện thành công mới được gửi; các trường hợp thất bại và hết thời gian chờ (timeout) sẽ không bao giờ được gửi.

🔒 Mọi hash chúng tôi gửi đều được xác minh on-chain trước. Một webhook chỉ được gửi đi sau khi từng mã băm giao dịch (transaction hash) trong đó được tìm thấy trong một khối. Nếu một hash chưa nằm trong khối, việc phân phối sẽ bị tạm giữ và kiểm tra lại mỗi 30 giây trong tối đa 5 phút; nếu nó không bao giờ vào khối, sẽ không có gì được gửi cho đơn hàng đó. Bạn sẽ không bao giờ nhận được một hash không tồn tại on-chain.

URL cơ sở của endpoint ​

https://netts.io/apiv2/webhooks

Tiêu đề Yêu cầu (Request Headers) ​

Tiêu đềBắt buộcMô tả
Content-TypeCó (đối với POST/PATCH)application/json
X-API-KEYCóKhóa API của bạn từ bảng điều khiển Netts
X-Real-IPCóĐịa chỉ IP từ danh sách trắng (whitelist) của bạn

user_id của bạn được suy ra từ khóa API — bạn không bao giờ phải truyền nó. Bạn chỉ có thể xem và sửa đổi các endpoint của chính bạn.


Endpoint chính và dự phòng ​

Bạn đăng ký tối đa hai endpoint, và mỗi endpoint có một role:

Vai tròMục đích
primaryĐịa chỉ mà mọi webhook được gửi tới.
backupDự phòng. Chỉ được sử dụng khi việc gửi tới primary thất bại sau khi đã thử lại hết số lần quy định.

Một đơn hàng được xác nhận duy nhất sẽ tạo ra một webhook duy nhất. Đây không phải là gửi đồng loạt (fan-out): cùng một sự kiện không bao giờ được gửi tới cả hai địa chỉ cùng lúc. Endpoint backup tồn tại để tăng khả năng chịu lỗi — nếu host chính của bạn không thể truy cập hoặc liên tục trả về mã không phải 2xx, việc gửi thông báo sẽ chuyển sang endpoint dự phòng thay vì bị hủy bỏ.

Endpoint đầu tiên bạn tạo sẽ trở thành primary, endpoint thứ hai trở thành backup. Bạn có thể truyền role một cách rõ ràng, hoặc hoán đổi chúng sau này bằng PATCH.

Tại sao không dùng URL riêng cho từng loại thao tác? Bởi vì loại sự kiện được truyền bên trong phần thân (body), trong trường event. Một trình xử lý, một lần kiểm tra chữ ký, và các loại sự kiện mới sẽ bắt đầu được gửi đến mà không cần bạn phải đăng ký thêm bất cứ điều gì mới.


Quản lý endpoint ​

Tạo — POST /apiv2/webhooks ​

Đăng ký một endpoint mới và trả về một secret chỉ hiển thị một lần duy nhất (hãy lưu lại — nó dùng để ký mọi webhook bạn nhận được).

json
// request body — role is optional
{
    "url": "https://your-server.example/netts/delegation-hook",
    "role": "primary"
}

Nếu bạn bỏ qua role, vai trò còn trống đầu tiên sẽ được chỉ định: primary, sau đó đến backup.

json
// response 201
{
    "detail": {
        "code": 10000,
        "status": "created",
        "data": {
            "id": 1,
            "url": "https://your-server.example/netts/delegation-hook",
            "secret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
            "role": "primary",
            "is_active": true,
            "created_at": "2026-01-01T00:00:00"
        }
    }
}

Yêu cầu về URL (được xác thực khi tạo và ở mỗi lần chỉnh sửa):

  • phải là https;
  • phải phân giải thành một địa chỉ công khai — loopback, mạng nội bộ (RFC1918), link-local (bao gồm 169.254.169.254), và các dải không thể định tuyến khác đều bị từ chối;
  • không chứa thông tin đăng nhập trong URL (user:pass@…);
  • độ dài tối đa 2048 ký tự.

URL bị từ chối sẽ trả về 400.

Bạn có thể có hai endpoint — một primary và một backup. Endpoint thứ ba sẽ trả về 409 (4090). Yêu cầu một role đã được sử dụng sẽ trả về 409 (4091) — hãy hoán đổi vai trò bằng PATCH hoặc xóa endpoint hiện có trước.

bash
curl -X POST https://netts.io/apiv2/webhooks \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: your_api_key" \
  -H "X-Real-IP: your_whitelisted_ip" \
  -d '{"url": "https://your-server.example/netts/delegation-hook", "role": "primary"}'

Liệt kê — GET /apiv2/webhooks ​

Trả về danh sách endpoint của bạn (secret không bao giờ được trả về ở đây).

json
{
    "detail": {
        "code": 10000,
        "status": "ok",
        "data": {
            "endpoints": [
                {
                    "id": 1,
                    "url": "https://your-server.example/netts/delegation-hook",
                    "role": "primary",
                    "is_active": true,
                    "created_at": "2026-01-01T00:00:00",
                    "updated_at": "2026-01-01T00:00:00"
                },
                {
                    "id": 2,
                    "url": "https://backup.example/netts/delegation-hook",
                    "role": "backup",
                    "is_active": true,
                    "created_at": "2026-01-01T00:00:00",
                    "updated_at": "2026-01-01T00:00:00"
                }
            ],
            "count": 2,
            "max_endpoints": 2,
            "roles": ["primary", "backup"]
        }
    }
}

Lấy thông tin một endpoint — GET /apiv2/webhooks/{id} ​

Cùng định dạng với một phần tử trong danh sách (không có secret). Một id của người khác hoặc không tồn tại sẽ trả về 404.

Chỉnh sửa — PATCH /apiv2/webhooks/{id} ​

Thay đổi url, is_active và/hoặc role. Gửi bất kỳ tập hợp con nào; phần thân rỗng sẽ trả về 422. Một url bị thay đổi sẽ được xác thực lại (https / SSRF). Một id của người khác hoặc không tồn tại sẽ trả về 404.

json
// request body (any subset)
{ "url": "https://your-server.example/netts/new-hook", "is_active": false }
json
// response 200
{
    "detail": {
        "code": 10000,
        "status": "updated",
        "data": {
            "id": 1,
            "url": "https://your-server.example/netts/new-hook",
            "role": "primary",
            "is_active": false,
            "created_at": "2026-01-01T00:00:00",
            "updated_at": "2026-01-01T00:00:01"
        }
    }
}

Nâng cấp endpoint dự phòng. Gửi {"role": "primary"} tới endpoint dự phòng của bạn sẽ hoán đổi hai vai trò trong một transaction duy nhất — primary cũ sẽ trở thành backup. Bạn sẽ không bao giờ bị rơi vào tình trạng thiếu địa chỉ primary, và không cần gọi API riêng cho endpoint còn lại.

bash
curl -X PATCH https://netts.io/apiv2/webhooks/2 \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: your_api_key" \
  -H "X-Real-IP: your_whitelisted_ip" \
  -d '{"role": "primary"}'

Đặt is_active: false để tạm dừng gửi mà không xóa endpoint; đặt true để tiếp tục. Tạm dừng primary của bạn không làm nâng cấp endpoint backup — việc phân phối vẫn nhắm vào primary. Hãy hoán đổi vai trò nếu bạn muốn endpoint backup tiếp quản.

Xoay vòng secret — POST /apiv2/webhooks/{id}/rotate-secret ​

Tạo một secret mới và trả về nó một lần duy nhất. Secret mới có hiệu lực ngay lập tức cho các lần phân phối tiếp theo — không cần làm thêm thao tác nào khác. Mỗi endpoint có secret của chính nó: việc xoay vòng secret của primary không làm thay đổi secret của backup.

json
// response 200
{
    "detail": {
        "code": 10000,
        "status": "rotated",
        "data": {
            "id": 1,
            "secret": "whsec_yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
        }
    }
}

Xóa — DELETE /apiv2/webhooks/{id} ​

Xóa hoàn toàn endpoint và giải phóng vai trò của nó. Trả về 204 (không có body); một id của người khác hoặc không tồn tại sẽ trả về 404.

bash
curl -X DELETE https://netts.io/apiv2/webhooks/1 \
  -H "X-API-KEY: your_api_key" -H "X-Real-IP: your_whitelisted_ip"

Các webhook chúng tôi phân phối ​

Khi một trong các đơn hàng của bạn được hoàn thành, Netts gửi một POST tới endpoint primary của bạn. Mọi phần thân đều là application/json (UTF-8); các địa chỉ và hash luôn là giá trị đầy đủ.

Các trường chung cho mọi sự kiện:

TrườngLoạiMô tả
eventstringLoại sự kiện — khóa định tuyến cho trình xử lý của bạn
delivery_idintID phân phối — khóa loại bỏ trùng lặp (dedup key) ở phía bạn. Cũng được gửi trong tiêu đề X-Netts-Delivery.
order_idstringID đơn hàng của bạn
order_typestring1h, 5m, bandwidth hoặc activation
tx_hashesstring[]Tất cả các hash giao dịch của thao tác, mỗi hash đều được xác minh on-chain
confirmed_atstringChuỗi UTC ISO-8601

delegation.confirmed — thuê energy ​

json
{
    "event": "delegation.confirmed",
    "delivery_id": 1,
    "order_id": "1Hxxxxxxxxxx",
    "order_type": "1h",
    "receive_address": "TXXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "energy_amount": 65000,
    "tx_hash": "0000000000000000000000000000000000000000000000000000000000000000",
    "tx_hashes": ["0000000000000000000000000000000000000000000000000000000000000000"],
    "delegation_timestamp": 1700000000000,
    "confirmed_at": "2026-01-01T00:00:00Z"
}
TrườngLoạiMô tả
order_typestring1h hoặc 5m
receive_addressstringĐịa chỉ TRON đã nhận energy
energy_amountintLượng Energy được ủy quyền
tx_hashstringTrường cũ (legacy), được giữ lại để tương thích: giống như tx_hashes[0]
delegation_timestampint?Tùy chọn — chỉ xuất hiện khi được xác nhận qua luồng Mongo

Ưu tiên sử dụng tx_hashes trong các tích hợp mới — về nguyên tắc, một đơn hàng có thể được hoàn thành bằng nhiều hơn một giao dịch. tx_hash vẫn sẽ tiếp tục hoạt động.

bandwidth.delegated — đơn hàng bandwidth ​

json
{
    "event": "bandwidth.delegated",
    "delivery_id": 2,
    "order_id": "B1Hxxxxxxxxxxxxx",
    "order_type": "bandwidth",
    "rental_label": "1h",
    "rental_seconds": 3600,
    "receive_address": "TXXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "bandwidth_amount": 400,
    "fulfillment": "delegated",
    "tx_hashes": ["0000000000000000000000000000000000000000000000000000000000000000"],
    "confirmed_at": "2026-01-01T00:00:00Z"
}
TrườngLoạiMô tả
rental_label / rental_secondsstring / intThời hạn thuê, ví dụ: 1h / 3600
receive_addressstringĐịa chỉ TRON đã nhận bandwidth
bandwidth_amountintĐơn vị Bandwidth (thực nhận)
fulfillmentstringCách đơn hàng được hoàn thành — xem bên dưới

Các giá trị của fulfillment:

Giá trịÝ nghĩatx_hashes
delegatedBandwidth được ủy quyền từ pool của chúng tôi1+ hash
trx_sendĐược hoàn thành bằng cách gửi TRX tới địa chỉ thay vì ủy quyền1+ hash
already_enoughĐịa chỉ đã có sẵn đủ bandwidth khả dụng — không có gì được gửi on-chainrỗng

already_enough là trường hợp duy nhất mà tx_hashes rỗng: đơn hàng được đóng thành công, nhưng không có giao dịch nào vì không cần thiết.

activation.confirmed — kích hoạt địa chỉ ​

json
{
    "event": "activation.confirmed",
    "delivery_id": 3,
    "order_id": "123456",
    "order_type": "activation",
    "address": "TXXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "activation_type": "ACC_CREATE",
    "source": "telegram_bot",
    "tx_hashes": ["0000000000000000000000000000000000000000000000000000000000000000"],
    "confirmed_at": "2026-01-01T00:00:00Z"
}
TrườngLoạiMô tả
order_idstringID đơn hàng kích hoạt (chuỗi số)
addressstringĐịa chỉ TRON đã được kích hoạt
activation_typestringACC_CREATE (AccountCreateContract) hoặc DIRECT (chuyển khoản TRX)
sourcestringĐánh dấu nguồn gốc. Có thể là thẻ dịch vụ, hoặc ID của đơn hàng energy yêu cầu việc kích hoạt

Chỉ các lần kích hoạt thực tế mới được gửi đi. Nếu địa chỉ hóa ra đã hoạt động từ trước và không có giao dịch nào được thực hiện, webhook sẽ hoàn toàn không được gửi.

Một đơn hàng energy đồng thời yêu cầu kích hoạt sẽ tạo ra hai webhook — một activation.confirmed và một delegation.confirmed. Chúng là các sự kiện riêng biệt với các delivery_id riêng biệt; hãy định tuyến chúng dựa trên trường event.

Các tiêu đề chúng tôi gửi:

Tiêu đềGiá trị
X-Netts-EventLoại sự kiện: delegation.confirmed, bandwidth.delegated hoặc activation.confirmed
X-Netts-Deliverydelivery_id (loại bỏ trùng lặp)
X-Netts-Timestampunix timestamp tính bằng giây tại thời điểm gửi
X-Netts-Signaturesha256=<hex>, hex = HMAC_SHA256(secret, "<timestamp>." + raw_body)
User-Agentnetts-webhook/1.0

Xác minh chữ ký ​

Chữ ký tuân theo cơ chế của Stripe (timestamp.body), được tính toán dựa trên các byte thô (raw bytes) mà chúng tôi gửi. Hãy tính toán lại chữ ký bằng secret của bạn, so sánh theo thời gian không đổi (constant-time), và từ chối nếu X-Netts-Timestamp nằm ngoài khoảng thời gian ±5 phút (bảo vệ chống tấn công phát lại - replay protection).

Ký bằng secret của endpoint đã nhận yêu cầu: primary và backup có các secret riêng biệt. Nếu cả hai địa chỉ của bạn được phục vụ bởi cùng một trình xử lý, hãy chọn secret dựa trên URL mà yêu cầu được gửi đến.

python
import hmac, hashlib, time

def verify(raw_body: bytes, sig_header: str, ts_header: str, secret: str) -> bool:
    # freshness (anti-replay)
    if abs(time.time() - int(ts_header)) > 300:
        return False
    signed = f"{ts_header}.".encode() + raw_body
    expected = "sha256=" + hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig_header)

# Flask example:
# ok = verify(request.get_data(),
#             request.headers["X-Netts-Signature"],
#             request.headers["X-Netts-Timestamp"], SECRET)

Ngữ nghĩa phân phối (quan trọng — at-least-once) ​

Phân phối theo cơ chế ít nhất một lần (at-least-once): một phản hồi bị rớt có thể kích hoạt việc thử lại, do đó bạn có thể nhận được cùng một sự kiện hai lần. Bởi vì hành động nghiệp vụ (giải phóng USDT) liên quan trực tiếp đến tiền tệ:

  1. Khử trùng lặp (dedup) là bắt buộc — xử lý mỗi sự kiện một cách idempotent dựa theo delivery_id (và/hoặc order_id); việc lặp lại không thực hiện thao tác nào (no-op).
  2. Xác minh HMAC trước bất kỳ hành động chuyển tiền nào — không tin cậy phần thân cho đến khi chữ ký khớp và X-Netts-Timestamp còn mới.
  3. Chỉ trả về 2xx sau khi bạn đã lưu trữ sự kiện một cách an toàn và bền vững — nếu không, chúng tôi sẽ thử lại (một cách chính xác).

Phản hồi 2xx để xác nhận đã nhận; bất kỳ mã nào không phải 2xx / timeout sẽ kích hoạt thử lại.

Thứ tự các lần thử:

  1. Các lần thử lại sẽ gửi đến endpoint primary của bạn. Khoảng thời gian phụ thuộc vào loại đơn hàng: các đơn hàng 5m thử lại trong ~1 phút, tất cả các loại khác trong ~10 phút.
  2. Nếu hết khoảng thời gian này và bạn đã đăng ký một backup, việc phân phối sẽ chuyển sang đó và lịch trình thử lại bắt đầu lại từ đầu — được ký bằng secret của chính backup.
  3. Chỉ sau khi endpoint backup cũng đã thử hết số lần, việc phân phối mới được đánh dấu là thất bại hoàn toàn (dead).

Cùng một delivery_id được sử dụng xuyên suốt, vì vậy một thông báo ban đầu thất bại trên primary và sau đó thành công trên backup vẫn là một sự kiện duy nhất đối với logic khử trùng lặp của bạn.


Tham chiếu Mã lỗi ​

MãMô tảMã trạng thái HTTP
10000Thành công (created / ok / updated / rotated)200 / 201
-Đã xóa (không có body)204
4000URL webhook không hợp lệ / không an toàn (không phải https, private/loopback, chứa thông tin xác thực, quá dài)400
-1Khóa API không hợp lệ / IP không nằm trong whitelist401
-1Không tìm thấy endpoint (hoặc không phải của bạn)404
4090Đã đạt giới hạn endpoint (tối đa 2: primary, backup)409
4091Vai trò được yêu cầu đã được sử dụng — hãy hoán đổi bằng PATCH hoặc xóa endpoint hiện có409
4220Không có gì để cập nhật (PATCH với phần thân rỗng)422
5003Không thể tạo endpoint (vui lòng thử lại)503

Giới hạn Tần suất (Rate Limits) ​

Bị giới hạn theo từng khóa API (tiêu đề X-API-KEY):

Khoảng thời gianGiới hạn
1 giây5 yêu cầu
1 phút150 yêu cầu

Vượt quá Giới hạn Tần suất (429) ​

json
{ "message": "API rate limit exceeded" }

Ghi chú ​

  • Secret chỉ hiển thị một lần — khi tạo và khi xoay vòng. Nó không bao giờ được trả về qua GET/LIST. Bị mất? Hãy xoay vòng để nhận một mã mới.
  • Hai endpoint, không phải fan-out: một primary và một backup. Mỗi đơn hàng được xác nhận sẽ tạo ra một webhook, được phân phối đến primary; backup chỉ được sử dụng nếu primary đã thử hết số lần quy định.
  • Thay đổi URL không gián đoạn (zero-downtime): đăng ký địa chỉ mới dưới dạng backup, xác minh nó, sau đó PATCH nó thành primary — việc hoán đổi mang tính nguyên tử (atomic).
  • Tạm dừng: PATCH … {"is_active": false} dừng phân phối mà không làm mất endpoint.
  • Chỉ các sự kiện thành công: delegation.confirmed, bandwidth.delegated, activation.confirmed. Không có sự kiện thất bại — một đơn hàng thất bại hoặc hết thời gian chờ sẽ không tạo ra webhook nào.
  • Các loại sự kiện mới có thể được thêm vào theo thời gian. Hãy định tuyến theo trường event và bỏ qua các loại bạn chưa xử lý — bạn không bao giờ cần phải đăng ký bất cứ điều gì mới để bắt đầu nhận chúng.
  • Các hash được xác minh on-chain trước khi gửi (xem ghi chú ở trên cùng): một webhook hoặc là mang các hash đều đã nằm trong một khối, hoặc sẽ không được gửi đi.
  • Các URL được xác thực về tính an toàn SSRF khi đăng ký và ở mỗi lần chỉnh sửa; phía phân phối sẽ xác thực lại tại thời điểm gửi.