API 文档
将 Cryptanio 连接到您的应用。
合同 v1 / 1.0.0 62 / 2026-10-10
目录
开始使用
API 供您的服务器使用。请先在 Account 中配置项目、服务和地址池;API 在这些范围内执行密钥授权的操作。
示例会对线上数据执行真实操作。创建或删除对象可能消耗服务配额。密钥从不嵌入示例,而是从环境变量读取。
给智能体的指令
将此指令粘贴到智能体的聊天或项目规则中。它帮助智能体了解服务、定价和参考文档,然后使用 REST API 或 SDK 编写应用集成。智能体可以使用您的语言回答。
Learn how to integrate a server-side application with Cryptanio's public REST API before proposing or writing integration code.
Start here:
- Website: https://cryptanio.com/
- Documentation index: https://cryptanio.com/llms.txt
- Product, services and pricing: https://cryptanio.com/cryptanio.md
- API integration index: https://cryptanio.com/docs/llms.txt
- API guide: https://cryptanio.com/docs.md
- Exact API v1 contract: https://cryptanio.com/openapi.json
- FAQ: https://cryptanio.com/faq/index.md
- SDK setup and examples: https://cryptanio.com/docs.html#sdk
Read the relevant method reference before using it. Use the public API at https://api.cryptanio.com/v1. Do not assume a public sandbox exists.
Use HTTP requests or the documented SDK for the application's language. Match request fields, response schemas and errors to OpenAPI; do not invent endpoints or SDK methods.
Check the actual enabled networks, service, project and API key permissions. Product documentation is not evidence of a live payment, balance or deployment.
Keep API keys in the user's server environment or secret store. Never ask the user to paste a key into chat or put it in a URL, browser bundle or log.
Default to reading. Create, change or delete objects only when the user authorizes the specific task. Explain quota or billing consequences before a write.
Follow the documented confirmation, webhook verification, pagination and retry rules. Reuse an invoice's original Idempotency-Key and request when retrying.
Show the returned chain, asset, address, amount and expiry. Preserve payment_route.kind and its exact string value: destination_tag, memo, comment or reference is mandatory when present. The merchant order reference is a separate field.
For a payment QR use the returned payment_uri when present. A Solana Pay reference is not a wallet memo; do not rebuild its URI or replace it with a plain address. Offer the exact returned checkout_url only to the payer; never construct a checkout URL from an invoice ID. Neither a checkout redirect nor a QR scan confirms payment.
Distinguish pending, confirmed, rolled-back and simulated data. If a fact is unavailable, say so instead of guessing.
Answer the user's questions in their language. If a necessary product decision is not documented, ask instead of inventing it.此段内容不含密钥或账户数据。请为集成使用最低所需权限的 API 密钥,并保存在应用服务器上。不要在智能体聊天中分享密钥。
API 参考
方法:16
Monitoring 监控
Payments
Webhook
API 健康状态
身份验证
从服务器发送 Authorization: Bearer <API_KEY>。在 Account → API 密钥中创建用于线上数据的密钥,仅授权所需项目和操作。目前不提供公开沙盒。
密钥可允许只读访问、指定操作、单个项目或服务,以及指定 IP/CIDR 范围。到期、暂停和撤销也影响访问。轮换密钥时,请创建具有适当限制的替代密钥,切换集成后再撤销旧密钥。切勿将密钥嵌入浏览器代码、移动应用或 URL。
| 权限范围 | 用途 |
|---|---|
watches | Monitoring |
events | 事件与付款 |
invoices | Payments 账单 |
webhooks | Webhook 端点 |
stream | SSE |
各项限制同时适用,不能相互替代。401 表示密钥未被接受;403 可能表示权限不足或 IP 不被允许。无权访问的资源可能返回 404。参见错误与重试。
SDK:即用型客户端
下载所用语言的文件,保存到应用服务器,并将密钥传入构造函数。每个客户端均覆盖本参考中的全部 16 个方法。无需安装 npm、PyPI、Composer 或 crates.io 软件包;下载的文件就是 SDK。
| 语言 | 要求 | 文件 |
|---|---|---|
| Node.js | Node.js 18+ | cryptanio.mjs |
| Python | Python 3.10+ | cryptanio.py |
| PHP | PHP 8.1+ / cURL | Cryptanio.php |
| Go | Go 1.22+ | cryptanio.go |
| Rust | Rust 2021 / reqwest 0.12 / serde_json 1 | cryptanio.rs |
这些文件属于同一版本 1.0.0 62。清单包含版本、合约和各文件的 SHA-256。请先在环境中设置 CRYPTANIO_API_KEY 和 CRYPTANIO_PROJECT_ID。这些示例仅读取事件,但访问的是线上账户数据。
Node.js
将示例保存为 .mjs 文件,与 cryptanio.mjs 放在一起。
import { Cryptanio } from './cryptanio.mjs';
const key = process.env.CRYPTANIO_API_KEY;
const project = process.env.CRYPTANIO_PROJECT_ID;
if (!key || !project) throw new Error('Set API key and project ID');
const api = new Cryptanio(key);
const page = await api.listEvents({ project, limit: 25 });
console.log(page);要读取现有发票的付款说明,请在CRYPTANIO_RESOURCE_ID中设置其ID。下一个示例会保留必需的payment_route、用于二维码的返回值payment_uri以及准确的checkout_url。通过自己的界面将结果交给付款人,不要写入公开日志。金额保持字符串格式,缺少的字段会被省略。
import { Cryptanio } from './cryptanio.mjs';
const key = process.env.CRYPTANIO_API_KEY;
const invoiceID = process.env.CRYPTANIO_RESOURCE_ID;
if (!key || !invoiceID) throw new Error('Set API key and invoice ID');
const api = new Cryptanio(key);
const invoice = await api.getInvoice(invoiceID);
const payment = {
chain: invoice.chain, asset: invoice.asset, address: invoice.address,
amount: invoice.amount, expires_at: invoice.expires_at,
payment_route: invoice.payment_route,
payment_uri: invoice.payment_uri, checkout_url: invoice.checkout_url,
};
console.log(JSON.stringify(payment, null, 2));Python
将示例保存为 .py 文件,与 cryptanio.py 放在一起。
import os
from cryptanio import Cryptanio
api = Cryptanio(os.environ['CRYPTANIO_API_KEY'])
page = api.list_events({
'project': os.environ['CRYPTANIO_PROJECT_ID'], 'limit': 25
})
print(page)要读取现有发票的付款说明,请在CRYPTANIO_RESOURCE_ID中设置其ID。下一个示例会保留必需的payment_route、用于二维码的返回值payment_uri以及准确的checkout_url。通过自己的界面将结果交给付款人,不要写入公开日志。金额保持字符串格式,缺少的字段会被省略。
import json
import os
from cryptanio import Cryptanio
api = Cryptanio(os.environ['CRYPTANIO_API_KEY'])
invoice = api.get_invoice(os.environ['CRYPTANIO_RESOURCE_ID'])
payment = {field: invoice[field] for field in (
'chain', 'asset', 'address', 'amount', 'expires_at',
'payment_route', 'payment_uri', 'checkout_url'
) if field in invoice}
print(json.dumps(payment, ensure_ascii=False))PHP
将示例保存为 .php 文件,与 Cryptanio.php 放在一起;启用 cURL 扩展。
<?php
require __DIR__ . '/Cryptanio.php';
$key = getenv('CRYPTANIO_API_KEY');
$project = getenv('CRYPTANIO_PROJECT_ID');
if (!$key || !$project) throw new RuntimeException('Set API key and project ID');
$api = new Cryptanio($key);
$page = $api->listEvents(['project' => $project, 'limit' => 25]);
echo json_encode($page, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR);要读取现有发票的付款说明,请在CRYPTANIO_RESOURCE_ID中设置其ID。下一个示例会保留必需的payment_route、用于二维码的返回值payment_uri以及准确的checkout_url。通过自己的界面将结果交给付款人,不要写入公开日志。金额保持字符串格式,缺少的字段会被省略。
<?php
require __DIR__ . '/Cryptanio.php';
$key = getenv('CRYPTANIO_API_KEY');
$invoiceID = getenv('CRYPTANIO_RESOURCE_ID');
if (!$key || !$invoiceID) throw new RuntimeException('Set API key and invoice ID');
$api = new Cryptanio($key);
$invoice = $api->getInvoice($invoiceID);
$payment = array_intersect_key($invoice, array_fill_keys([
'chain', 'asset', 'address', 'amount', 'expires_at',
'payment_route', 'payment_uri', 'checkout_url'
], true));
echo json_encode($payment, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR);Go
将文件放入 Go 模块内的 cryptanio/ 目录。本示例使用模块 example.local/myapp;请将导入路径替换为您自己的模块名称。无需外部依赖。
package main
import (
"context"
"fmt"
"os"
"example.local/myapp/cryptanio"
)
func main() {
key, project := os.Getenv("CRYPTANIO_API_KEY"), os.Getenv("CRYPTANIO_PROJECT_ID")
if key == "" || project == "" { panic("Set API key and project ID") }
api, err := cryptanio.New(key, "", 0)
if err != nil { panic(err) }
page, err := api.ListEvents(context.Background(), map[string]any{
"project": project, "limit": 25,
})
if err != nil { panic(err) }
fmt.Println(page)
}要读取现有发票的付款说明,请在CRYPTANIO_RESOURCE_ID中设置其ID。下一个示例会保留必需的payment_route、用于二维码的返回值payment_uri以及准确的checkout_url。通过自己的界面将结果交给付款人,不要写入公开日志。金额保持字符串格式,缺少的字段会被省略。
package main
import (
"context"
"encoding/json"
"os"
"example.local/myapp/cryptanio"
)
func main() {
key, invoiceID := os.Getenv("CRYPTANIO_API_KEY"), os.Getenv("CRYPTANIO_RESOURCE_ID")
if key == "" || invoiceID == "" { panic("Set API key and invoice ID") }
api, err := cryptanio.New(key, "", 0)
if err != nil { panic(err) }
invoice, err := api.GetInvoice(context.Background(), invoiceID)
if err != nil { panic(err) }
payment := make(map[string]any)
for _, field := range []string{
"chain", "asset", "address", "amount", "expires_at",
"payment_route", "payment_uri", "checkout_url",
} {
if value, ok := invoice[field]; ok { payment[field] = value }
}
if err := json.NewEncoder(os.Stdout).Encode(payment); err != nil { panic(err) }
}Rust
将文件保存为 src/cryptanio.rs,示例保存为 src/main.rs。将下列依赖加入 Cargo.toml。这是同步阻塞客户端:不要直接在异步工作线程中运行。
[dependencies]
reqwest = { version = "=0.12.24", default-features = false, features = ["blocking", "json", "rustls-tls"] }
serde_json = "=1.0.145"mod cryptanio;
use cryptanio::Cryptanio;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let api = Cryptanio::new(&std::env::var("CRYPTANIO_API_KEY")?)?;
let project = std::env::var("CRYPTANIO_PROJECT_ID")?;
let page = api.list_events(&[("project", &project), ("limit", "25")])?;
println!("{page}");
Ok(())
}要读取现有发票的付款说明,请在CRYPTANIO_RESOURCE_ID中设置其ID。下一个示例会保留必需的payment_route、用于二维码的返回值payment_uri以及准确的checkout_url。通过自己的界面将结果交给付款人,不要写入公开日志。金额保持字符串格式,缺少的字段会被省略。
mod cryptanio;
use cryptanio::Cryptanio;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let api = Cryptanio::new(&std::env::var("CRYPTANIO_API_KEY")?)?;
let invoice = api.get_invoice(&std::env::var("CRYPTANIO_RESOURCE_ID")?)?;
let mut payment = serde_json::Map::new();
for field in ["chain", "asset", "address", "amount", "expires_at",
"payment_route", "payment_uri", "checkout_url"] {
if let Some(value) = invoice.get(field) {
payment.insert(field.to_owned(), value.clone());
}
}
println!("{}", serde_json::Value::Object(payment));
Ok(())
}调用方法
Node.js 和 PHP 使用 listEvents,Python 和 Rust 使用 list_events,Go 使用 ListEvents。每个方法对应参考文档中的一个请求。筛选条件为对象或映射,Rust 中为键值对列表。Go 方法始终先接收 context.Context。未知筛选条件在发送请求前会被拒绝。
| Node.js / PHP 方法 | HTTP |
|---|---|
createWatch | POST /v1/watches |
listWatches | GET /v1/watches |
getWatch | GET /v1/watches/{id} |
deleteWatch | DELETE /v1/watches/{id} |
listEvents | GET /v1/events |
listPayments | GET /v1/payments |
createInvoice | POST /v1/invoices |
listInvoices | GET /v1/invoices |
getInvoice | GET /v1/invoices/{id} |
cancelInvoice | POST /v1/invoices/{id}/cancel |
createEndpoint | POST /v1/endpoints |
listEndpoints | GET /v1/endpoints |
deleteEndpoint | DELETE /v1/endpoints/{id} |
streamEvents | GET /v1/stream |
getHealth | GET /v1/health |
listChains | GET /v1/chains |
创建监控和 webhook 需要请求正文。单资源方法接收其字符串 ID。createInvoice(body, idempotencyKey) 要求使用由 ASCII 字母、数字、连字符或下划线组成的 16–80 字符密钥。重试时复用相同密钥和正文;SDK 不会生成新密钥,也不会自动重试。正文、错误和权限请参阅各方法页面。
响应、错误与流式传输
JSON 响应保留原字段名;ID、金额和游标仍为字符串。分页按各方法文档手动进行。错误提供 HTTP 状态、code、message 和 Retry-After。没有 HTTP 响应时,状态为 0(Rust 使用 None)。不跟随重定向。普通 JSON 响应上限为 8 MiB。默认超时为 30 秒,可配置至 300 秒。Python 中它适用于单次网络操作;其他客户端中覆盖整个请求,包括流式传输。超时并不能证明写操作未执行。
| 语言 / 错误 | 字段 |
|---|---|
| Node.js / Python: ApiError | status, code, message, retry_after |
| PHP: CryptanioError | status, apiCode, getMessage(), retryAfter |
| Go: *APIError | Status, Code, Message, RetryAfter |
| Rust: ApiError | status: Option<u16>, code, message, retry_after |
streamEvents 提供原始 SSE 字节,不负责重连或事件解析:Node.js ReadableStream、可关闭的 Python HTTP 响应、Go io.ReadCloser 或 Rust blocking::Response。不再需要时释放流。PHP 将数据块传给回调;返回 false 可停止。流式传输不能替代 webhook,也不保证历史重放;参见 SSE 规则。
您的第一个监控
在 Account 中创建已启用 Monitoring 的项目,以及具有 watches 和 events 权限的密钥。按要求完成账户验证。从项目设置复制 ID,只添加您确实希望监控的地址。
在服务器环境中设置 CRYPTANIO_API_KEY 和 CRYPTANIO_PROJECT_ID。将示例地址替换为您自己的地址。此请求会关联真实监控,不会模拟交易。
#!/usr/bin/env bash
set -euo pipefail
: "${CRYPTANIO_API_KEY:?Set CRYPTANIO_API_KEY}"
: "${CRYPTANIO_PROJECT_ID:?Set CRYPTANIO_PROJECT_ID}"
json_string() {
local text="$1" char code index output='"'
for ((index=0; index<${#text}; index++)); do
char="${text:index:1}"
case "$char" in
'"'|'\') output+="\\$char" ;;
*) printf -v code '%d' "'$char"
if ((code < 32)); then printf -v char '\\u%04x' "$code"; fi
output+="$char" ;;
esac
done
printf '%s"' "$output"
}
response_file="$(mktemp)"
trap 'rm -f "$response_file"' EXIT
transfer_status=0
http_status="$(curl \
--silent --show-error --max-time 30 --request POST \
--header "Authorization: Bearer $CRYPTANIO_API_KEY" \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data-binary '{"project":'"$(json_string "$CRYPTANIO_PROJECT_ID")"',"chain":"ethereum","address":"0x52908400098527886E0F7030069857D2E4169EE7","asset":"native","label":"Treasury"}' \
"https://api.cryptanio.com/v1/watches" \
--output "$response_file" --write-out '%{http_code}')" || transfer_status=$?
if [[ "$http_status" == 2?? ]]; then
cat "$response_file"
exit "$transfer_status"
fi
printf 'HTTP %s\n' "$http_status" >&2
cat "$response_file" >&2
exit 1
成功响应包含 id,请保存。GET /v1/watches/{id} 返回监控对象和设置,而非付款。要查看已检测活动,请请求 GET /v1/events 并按 watch_id 筛选。检测到交易前列表为空是正常的。响应、错误及其他语言示例。
Payments 账单
为项目启用 Payments,并在 Account 中向地址池添加受支持地址。请明确发送 project,不要依赖默认项目选择。amount 是以加密资产为单位的字符串;invoice_currency/invoice_amount 单独描述订单报价。
将 CRYPTANIO_PROJECT_ID 设为您的 Payments 项目,将 CRYPTANIO_API_KEY 设为具有 invoices 权限的密钥。新订单的 CRYPTANIO_OPERATION_ID 必须唯一;重试同一操作时复用它。账单创建于线上数据中。
#!/usr/bin/env bash
set -euo pipefail
: "${CRYPTANIO_API_KEY:?Set CRYPTANIO_API_KEY}"
: "${CRYPTANIO_OPERATION_ID:?Set CRYPTANIO_OPERATION_ID}"
: "${CRYPTANIO_PROJECT_ID:?Set CRYPTANIO_PROJECT_ID}"
json_string() {
local text="$1" char code index output='"'
for ((index=0; index<${#text}; index++)); do
char="${text:index:1}"
case "$char" in
'"'|'\') output+="\\$char" ;;
*) printf -v code '%d' "'$char"
if ((code < 32)); then printf -v char '\\u%04x' "$code"; fi
output+="$char" ;;
esac
done
printf '%s"' "$output"
}
response_file="$(mktemp)"
trap 'rm -f "$response_file"' EXIT
transfer_status=0
http_status="$(curl \
--silent --show-error --max-time 30 --request POST \
--header "Authorization: Bearer $CRYPTANIO_API_KEY" \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header "Idempotency-Key: $CRYPTANIO_OPERATION_ID" \
--data-binary '{"project":'"$(json_string "$CRYPTANIO_PROJECT_ID")"',"invoice_currency":"USD","invoice_amount":"12.50","reference":"order-42"}' \
"https://api.cryptanio.com/v1/invoices" \
--output "$response_file" --write-out '%{http_code}')" || transfer_status=$?
if [[ "$http_status" == 2?? ]]; then
cat "$response_file"
exit "$transfer_status"
fi
printf 'HTTP %s\n' "$http_status" >&2
cat "$response_file" >&2
exit 1
密钥必须允许 invoices 和 Payments;Monitoring 示例密钥可能被限制在其他服务。Idempotency-Key 包含 16-80 个 ASCII 字母、数字、- 和 _。使用同一密钥重复相同的规范化请求,会返回现有账单和 201;内容改变时返回 409 conflict。操作密钥在整个账户内生效。reference 不能替代它。
向买方显示返回的网络、资产、地址、amount和到期时间。如果存在payment_route,还必须传递其kind和准确的字符串值value:destination_tag、memo、comment或reference是将付款归属到该发票的必需信息。订单的reference字段是另一个独立字段。
如果返回了payment_uri,请将其原样用于付款二维码。Solana Pay reference不是钱包memo:不要用普通地址替换URI,也不要重新拼接URI。如果钱包无法携带必需的标识,请向付款人提供checkout_url或兼容的钱包。
checkout_url是新开发票的托管付款页面。只向付款人分享返回的准确URL,不要根据ID构造链接。此链接可显示付款详情和状态,因此不要写入公开日志。如果没有该字段,请显示完整的付款说明。浏览器跳转不能证明付款:请检查API或经过验证的webhook。
为区分订单,asked可能与amount不同。请将amount_raw作为任意精度整数进行核对。默认TTL为60分钟,最多10080分钟。发票列表接受before,但不返回next_before:使用最后一项的ID继续查询,遇到空页时停止。
Customer
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | string | 是 | 您系统中的客户标识符。 maxLength: 120 |
Product
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | string | 否 | 您系统中的产品标识符。 maxLength: 120 |
name | string | 否 | 产品名称。 maxLength: 200 |
quantity | integer | 否 | 数量。 minimum: 1; maximum: 1000000 |
Payer
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
comment | string | 是 | 付款人留言。 maxLength: 500 |
InvoiceMetadata
最多 20 对,总计不超过 8192 个 JSON 字节。不要包含密钥。
object 最多 20 对,总计不超过 8192 个 JSON 字节。不要包含密钥。
Monitoring 监控
监控对象属于一个 Monitoring 项目。同一地址可在多个项目中使用,各自配置独立的方向和资产规则。请在 Account 中连接地址簿列表和 Address Sources;此 REST API 不提供其管理命令。
列表按 ID 降序排列。若存在 next_before,请在下一请求中作为 before 传入,并保持项目和筛选条件不变。limit 默认值为 50,上限为 200。将 ID 保存为字符串。移除监控会使其退出有效列表;此处移除不会从其他项目或地址簿删除该地址。
事件与历史
/v1/events 返回密钥可访问的跨服务事件;/v1/payments 仅限 Payments。可按 project、address、chain、type、watch_id 和 tx_id 筛选。before 是 ID 游标,不是日期;没有 from/to 筛选。next_before 可获取下一页。
检测到的事件历史最多可查询 30 天。这并不是所有已投递消息的档案:不保证能通过它恢复 notice 和 webhook.test 消息。REST Event 使用独立于 webhook 的结构;v1 内可能新增字段。
Event
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
event_id | string | 是 | 不透明标识符;请保持为字符串。 minLength: 1 |
event_type | string | 是 | 事件类型;可能添加新类型。 |
chain | string | 是 | 网络代码。 |
service | string | 否 | 已知时的服务。 |
watch_id | string | 否 | 关联监控时的项目监控 ID。 minLength: 1 |
occurred_at | string | 是 | RFC 3339 格式的 UTC 日期和时间。date-time |
payment | object | null | 是 | 付款文档;非付款事件为 null。GET /events 不返回 contract/notice 载荷。 |
sandbox | boolean | 否 | 测试事件;省略时为 false。 |
EventPayment
REST/SSE 付款文档。字段取决于事件和网络;不是标准化的 webhook v1 结构。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
tx_id | string | 否 | 交易标识符。 |
index | integer | 否 | 交易内的转账索引。 minimum: 0 |
height | integer | 否 | 可确定时的区块高度。 minimum: 0 |
address | string | 否 | 被观察的地址。 |
asset | string | 否 | REST/SSE asset:字符串标识符,不是 webhook 中的 asset 对象。 |
amount_raw | string | 否 | 以字符串表示的最小单位整数金额。 pattern: ^[0-9]+$ |
amount | string | 否 | 精确十进制金额字符串;不要使用浮点数。 pattern: ^[0-9]+(?:\.[0-9]+)?$ |
decimals | integer | 否 | 小数位数。 minimum: 0 |
confirmations | integer | 否 | 确认数。 minimum: 0 |
revision | integer | 否 | 转账状态修订号。 minimum: 0 |
direction | string | 否 | 相对于被观察地址的方向。in、out |
simulated | boolean | 否 | 模拟事件。 |
late | boolean | 否 | 延迟检测到的转账。 |
block_hash | string | 否 | 区块哈希。 |
match_key | string | 否 | 网络使用时的匹配标识符。 |
meta | object | 否 | |
invoice | object | 否 | |
customer | object | 否 | |
product | object | 否 | |
payer | object | 否 | |
metadata | object | 否 |
Webhook 事件
投递采用至少一次语义,因此事件可能多次到达。验证签名后,在集成中按 event_id 去重。将处理记录和业务变更持久化在同一个数据库事务中。tx_id/address/asset 组合不能替代 event_id,因为它会合并不同状态变化、方向和项目。
从已签名正文读取 event_type 和 event_id。X-Event-Type/X-Event-Id 请求头不包含在 HMAC 中;若使用它们,请与正文比较。更新付款时,请考虑 project_id、direction 和 payment.revision。不要假设投递有序;安全忽略未知类型,同时保留以供诊断。
| 类别 | 正文 |
|---|---|
| payment.pending / confirmed / rolled_back | 转入付款 |
| debit.pending / confirmed / rolled_back | 转出付款 |
| contract.approval / owner_added / owner_removed / threshold_changed / permissions_changed | contract |
| asset.unexpected | contract;该资产不会计为已接受的付款 |
| account.notice / account.notification / treasury.alarm / subscription.notice | notice.subject, notice.text |
| webhook.test | 不含付款的控制事件 |
schema_version: v1 根据事件类型承载 payment、contract 或 notice;webhook.test 不含这些内容。ID 和金额均为字符串。amount_raw 表示最小单位;amount 为十进制表示。通过 chains 检查网络和资产。完整对象结构见 OpenAPI。
| 请求头 | 含义 |
|---|---|
| X-Timestamp | 签名时的 Unix 秒数 |
| X-Signature | hex HMAC-SHA256(timestamp + '.' + raw body) |
| X-Event-Id / X-Event-Type | 与已签名的正文比较。 |
| X-Attempt | 从 1 开始的尝试次数 |
持久接收事件后返回 2xx。单次尝试超时为 10 秒。网络失败、408、425、429 和 5xx 最多重试 24 小时,延迟从 2 秒至 15 分钟,并加入随机抖动;遵守 Retry-After。不跟随重定向。其他 3xx/4xx 会结束投递;410 会停用目标及其积压消息。
{
"schema_version": "v1",
"event_id": "2300",
"event_type": "payment.confirmed",
"occurred_at": "2026-10-06T10:00:00Z",
"project_id": "1002",
"service": "monitoring",
"mode": "test",
"sandbox": true,
"payment": {
"chain": "ethereum",
"direction": "in",
"asset": {
"symbol": "ETH"
},
"address": "0x52908400098527886E0F7030069857D2E4169EE7",
"tx_id": "simulated_2300",
"index": 0,
"amount": "1.000000000000000000",
"amount_raw": "1000000000000000000",
"decimals": 18,
"confirmations": 0,
"revision": 1,
"simulated": true
}
}Webhook
公开签名 webhook v1 正文。payment.* 和 debit.* 包含 payment;contract.* 和 asset.unexpected 包含 contract;webhook.test 不含业务载荷;account.notice、account.notification、treasury.alarm 和 subscription.notice 包含 notice。按 event_id 对投递重试去重。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
schema_version | string | 是 | Webhook 载荷版本。v1 |
event_id | string | 是 | 事件 ID:投递重试时不变。不同状态可能具有不同的 event_id 值。 minLength: 1 |
event_type | string | 是 | 已签名正文中的类型;验证签名后再使用。可能添加新类型。 |
occurred_at | string | 是 | RFC 3339 格式的 UTC 日期和时间。date-time |
project_id | string | 是 | 不透明标识符;请保持为字符串。 minLength: 1 |
service | string | 否 | 适用时的项目服务。 |
mode | string | 是 | 事件模式。live、test |
sandbox | boolean | 是 | 明确的模拟标志。 |
payment | object | 否 | |
contract | object | 否 | |
invoice | object | 否 | |
customer | object | 否 | |
product | object | 否 | |
payer | object | 否 | |
metadata | object | 否 | |
notice | object | 否 |
WebhookPayment
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
chain | string | 是 | 网络代码。 |
direction | string | 是 | 转入付款:in;转出扣款:out。in、out |
asset | object | 是 | |
address | string | 是 | 被观察的地址。 |
tx_id | string | 是 | 交易 ID。 |
index | integer | 是 | 转账索引;不要假设任一网络均为 0。 minimum: 0 |
height | integer | 否 | 已知时的区块高度。 minimum: 0 |
block_hash | string | 否 | 已知时的区块哈希。 |
match_key | string | 否 | 适用时的网络匹配标识符。 |
amount | string | 是 | 精确十进制金额字符串;不要使用浮点数。 pattern: ^[0-9]+(?:\.[0-9]+)?$ |
amount_raw | string | 是 | 以字符串表示的最小单位整数金额。 pattern: ^[0-9]+$ |
decimals | integer | 是 | 小数位数。 minimum: 0 |
confirmations | integer | 是 | 确认数。 minimum: 0 |
revision | integer | 是 | 转账状态修订号。不要按交易标识合并不同的 event_id 值。 minimum: 0 |
late | boolean | 否 | 延迟检测到的转账;不等同于账单的 late 状态。 |
simulated | boolean | 否 | 模拟转账。 |
valuation | object | 否 |
WebhookAsset
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
symbol | string | 否 | 已知时的符号;不足以确定代币身份。 |
standard | string | 否 | 已知时的代币标准。 |
contract | string | 否 | 适用时,代币在其网络中的合约。 |
identifier | string | 否 | 其他网络专用的代币标识符。 |
WebhookContract
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
chain | string | 是 | 网络代码。 |
address | string | 是 | 被观察的地址或合约。 |
tx_id | string | 是 | 交易 ID。 |
index | integer | 是 | 事件索引。 minimum: 0 |
height | integer | 否 | 区块高度。 minimum: 0 |
block_hash | string | 否 | 区块哈希。 |
kind | string | 否 | 变更类型。 |
asset | string | 否 | 受影响的资产标识(如已知)。 |
detail | object | 否 | |
simulated | boolean | 否 | 模拟事件。 |
WebhookInvoice
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | string | 是 | 不透明标识符;请保持为字符串。 minLength: 1 |
reference | string | 否 | 订单参考编号。 |
status | string | 否 | pending 等待付款;seen 已检测;partial 部分付款;paid 已结清;underpaid/overpaid/late 需要作出处理决定;expired 已过期限;canceled 已作废。pending、seen、paid、underpaid、partial、overpaid、expired、late、canceled |
currency | string | 否 | 订单报价币种。 |
amount | string | 否 | 精确十进制金额字符串;不要使用浮点数。 pattern: ^[0-9]+(?:\.[0-9]+)?$ |
Notice
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
subject | string | 是 | 通知主题。 |
text | string | 是 | 通知文本。 |
Valuation
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
currency | string | 是 | 估值币种,例如 USD。 |
amount | string | 是 | 精确十进制金额字符串;不要使用浮点数。 pattern: ^[0-9]+(?:\.[0-9]+)?$ |
rate | string | 是 | 以十进制字符串表示的资产汇率。 pattern: ^[0-9]+(?:\.[0-9]+)?$ |
rate_at | string | 是 | RFC 3339 格式的 UTC 日期和时间。date-time |
provider | string | 否 | 已知时的报价来源名称。 |
验证签名
在解析 JSON 前,对原始正文的全部字节验证 HMAC,包括空格和换行。时效检查限制重放,但不能替代持久化的 event_id 去重。示例仅接受 64 字符的十六进制签名,以及与服务器时间相差不超过 300 秒的时间戳。请使用对应 webhook 目标的密钥。
import { createHmac, timingSafeEqual } from "node:crypto";
import { TextDecoder } from "node:util";
// rawBody is the original HTTP request Buffer, before JSON.parse or serialization.
export function verifyWebhook(secret, timestamp, rawBody, signature, nowSeconds = Math.floor(Date.now() / 1000)) {
if (typeof secret !== "string" || secret.length === 0 || !Buffer.isBuffer(rawBody) || rawBody.length === 0 ||
typeof timestamp !== "string" || !/^(0|[1-9][0-9]*)$/.test(timestamp) ||
typeof signature !== "string" || !/^[0-9a-f]{64}$/i.test(signature) ||
!Number.isSafeInteger(nowSeconds) || nowSeconds < 0) return false;
const signedAt = Number(timestamp);
if (!Number.isSafeInteger(signedAt) || Math.abs(nowSeconds - signedAt) > 300) return false;
const expected = createHmac("sha256", secret).update(timestamp).update(".").update(rawBody).digest();
return timingSafeEqual(expected, Buffer.from(signature, "hex"));
}
// headers is Node.js request.headers (lowercase names). Optional X-Event-Id/Type
// headers are not signed separately; compare them with the verified body.
export function readSignedWebhook(secret, timestamp, rawBody, signature, headers = {}, nowSeconds) {
if (!verifyWebhook(secret, timestamp, rawBody, signature, nowSeconds) ||
headers === null || typeof headers !== "object" || Array.isArray(headers)) return null;
let event;
try {
event = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(rawBody));
} catch {
return null;
}
if (event === null || typeof event !== "object" || Array.isArray(event) || event.schema_version !== "v1" ||
typeof event.event_id !== "string" || event.event_id.length === 0 ||
typeof event.event_type !== "string" || event.event_type.length === 0) return null;
for (const [header, field] of [["x-event-id", "event_id"], ["x-event-type", "event_type"]]) {
if (headers[header] !== undefined && headers[header] !== event[field]) return null;
}
return event;
}
package webhook
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"strconv"
"time"
"unicode/utf8"
)
// VerifyWebhook checks the original HTTP body bytes before parsing JSON.
// Optional nowSeconds is used only for deterministic tests.
func VerifyWebhook(secret, timestamp string, rawBody []byte, signature string, nowSeconds ...int64) bool {
if secret == "" || len(rawBody) == 0 || len(signature) != 64 || len(nowSeconds) > 1 {
return false
}
signedAt, err := strconv.ParseInt(timestamp, 10, 64)
if err != nil || signedAt < 0 || strconv.FormatInt(signedAt, 10) != timestamp {
return false
}
now := time.Now().Unix()
if len(nowSeconds) == 1 {
now = nowSeconds[0]
}
if now < 0 || now-signedAt < -300 || now-signedAt > 300 {
return false
}
provided, err := hex.DecodeString(signature)
if err != nil || len(provided) != sha256.Size {
return false
}
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(timestamp))
mac.Write([]byte("."))
mac.Write(rawBody)
return hmac.Equal(mac.Sum(nil), provided)
}
// WebhookEvent contains only routing fields. Parse other fields from rawBody
// after ReadSignedWebhook succeeds; unknown fields are allowed.
type WebhookEvent struct {
SchemaVersion string `json:"schema_version"`
EventID string `json:"event_id"`
EventType string `json:"event_type"`
}
// ReadSignedWebhook extracts event_id/type only from the verified body.
// X-Event-Id/Type are not signed separately; nonempty values must match the body.
func ReadSignedWebhook(secret, timestamp string, rawBody []byte, signature, eventIDHeader, eventTypeHeader string,
nowSeconds ...int64) (*WebhookEvent, error) {
if !VerifyWebhook(secret, timestamp, rawBody, signature, nowSeconds...) {
return nil, errors.New("invalid webhook signature or timestamp")
}
var event WebhookEvent
if !utf8.Valid(rawBody) || json.Unmarshal(rawBody, &event) != nil || event.SchemaVersion != "v1" ||
event.EventID == "" || event.EventType == "" {
return nil, errors.New("invalid webhook body")
}
if eventIDHeader != "" && eventIDHeader != event.EventID || eventTypeHeader != "" && eventTypeHeader != event.EventType {
return nil, errors.New("webhook headers do not match the signed body")
}
return &event, nil
}
Webhook 端点
为指定项目添加端口 443 上的公共 HTTPS URL。不允许 URL 凭据、片段、重定向和非公共目标。每个账户最多 20 个端点。各端点拥有独立秘密值,只在首次创建时返回,请保存在服务器上。
在相同项目和模式下再次创建同一 URL,会返回 200 和不含密钥的现有端点。新端点返回 201。DELETE 返回 200 以及 id 和 deleted: true,安全重复调用时亦然。密钥仅能查看自身模式和获准项目;投递遵循项目设置。
SSE 流
SSE 用于服务端集成和实时展示;它不保证投递,也不会重放遗漏事件。请使用带 Bearer 请求头的服务端 HTTP 客户端。标准浏览器 EventSource 无法发送此请求头;不要把 API 密钥放入浏览器代码或查询字符串。
消息使用 event: payment 和自身的 StreamEvent 结构,而非 webhook v1 或 REST Event。注释以冒号开头;心跳约每 20 秒到达。retry: 5000 是重连提示。断开后,请通过 events 获取仍可查询的区块链事件历史。
StreamEvent
SSE data 中的 JSON;与 webhook v1 不同。非付款事件省略 payment,可能包含 contract。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
event_id | string | 是 | 不透明标识符;请保持为字符串。 minLength: 1 |
event_type | string | 是 | 事件类型;可能添加新类型。 |
chain | string | 是 | 网络代码。 |
service | string | 否 | 已知时的服务。 |
watch_id | string | 否 | 关联监控时的项目监控 ID。 minLength: 1 |
occurred_at | string | 是 | RFC 3339 格式的 UTC 日期和时间。date-time |
payment | object | null | 否 | 付款文档;非付款事件为 null。GET /events 不返回 contract/notice 载荷。 |
sandbox | boolean | 否 | 测试事件;省略时为 false。 |
project_id | string | 否 | 不透明标识符;请保持为字符串。 minLength: 1 |
project_name | string | 否 | 项目名称。 |
url | string | 否 | 指向 Account 事件的链接。uri |
contract | object | 否 | |
mode | string | 否 | live 或 test。live、test |
付款与账单状态
区块链付款状态与订单状态不同。payment.pending 表示确认数不足时检测到付款,payment.confirmed 表示达到网络或项目的确认规则,payment.rolled_back 表示发生回滚。不要根据 pending、转出扣款或模拟事件履行订单。
| Invoice | 含义 |
|---|---|
| pending | 等待付款 |
| partial | 已收到部分款项;依照项目的部分付款政策处理 |
| paid | 付款已接受 |
| underpaid | 少付金额需要作出处理决定 |
| overpaid | 多付金额需要依照适用政策作出处理决定 |
| late | 到期后付款需要作出处理决定 |
| expired | 付款窗口已到期 |
| canceled | 账单已取消;记录仍可读取。 |
underpaid、overpaid 和 late 时存在 needs_decision。请在 Account 中处理这些状态。链重组和重新纳入区块可能产生新的状态变化及新的 payment.revision;confirmed 并不无条件保证未来不会变化。
错误与安全重试
{"error":{"code":"forbidden","message":"this API key is read-only"}}业务逻辑使用 HTTP 状态和 error.code,诊断使用 message。成功列表包含 data;单个对象直接返回。Health 使用自身的 status/version 结构,在 503 时也如此。不要依赖 message 的确切措辞。
| HTTP | 操作 |
|---|---|
| 400 | 修正 JSON、格式和参数;不要原样重试。 |
| 401 | 检查密钥、到期、一时停用和撤销状态。 |
| 403 | 检查权限范围、读写模式、项目、服务、IP 和账户验证。 |
| 404 | 检查 ID、密钥模式和访问权限;限制可能使资源不可见。 |
| 402 | Monitoring 监控数量限制;请检查套餐。 |
| 409 | 冲突、服务未启用、地址池已占用或达到限制;请检查错误代码。 |
| 429 | 遵守 Retry-After;降低账户所有请求的合计速率 |
| 500 / 503 | 使用有界退避重试;创建账单只能使用原始 Idempotency-Key |
超时不代表操作没有执行。重试时不要改变账单的操作键。没有防重复保护的方法应先读取状态;不要假定所有 POST 都具有幂等性。
网络与限制
使用所选 API 主机上的 chains 获取其网络、资产、能力和确认数目录。出现在通用目录中并不保证网络在该环境启用,或支持所有操作。在 Account 中检查服务配额和预算;价格见定价页面。
账户的已认证 REST 配额为:无付费服务时每秒 10 个请求,有付费服务时每秒 50 个请求;短时突发容量为 20/100。账户的所有密钥和模式共享此配额。超额会返回 429 及 Retry-After。服务配额与 API 速率限制是独立的限制。
API 版本
本参考描述版本 1.0.0 62 中的 v1;更新于 2026-10-10。使用新能力前,请通过 GET /v1/health 检查已部署版本。文档版本不能证明部署已完成。
| 公开 API | 合同 |
|---|---|
https://api.cryptanio.com | /v1 |
稳定集成请使用 /v1。无版本路径会选择环境当前的稳定合约;未知 /vN 返回 404。X-API-Version 标识合约;X-Server-Version 和 health.version 标识产品版本,不代表另一种 API 合约。
API 变更
版本 1.0.0 62 提供可下载的 Node.js、Python、PHP、Go 和 Rust SDK,以及面向 AI 智能体的本地 MCP。参考文档、Markdown 和原始 HTTP 示例仍独立提供。不提供公开沙盒。API 行为不变。
在 v1 内,客户端应容忍新增响应字段。接受未知事件类型不代表履行订单或计入付款。使用变更前,请根据所选环境的版本检查其可用性。