API 文档

将 Cryptanio 连接到您的应用。

合同 v1 / 1.0.0 62 / 2026-10-10

目录

开始使用

API 供您的服务器使用。请先在 Account 中配置项目、服务和地址池;API 在这些范围内执行密钥授权的操作。

  1. 验证账户,并在我的服务中启用所需服务。
  2. 创建仅具备最低所需权限的 API 密钥,并保存在服务器上。
  3. 选择任务:监控地址、创建账单或接收事件。

示例会对线上数据执行真实操作。创建或删除对象可能消耗服务配额。密钥从不嵌入示例,而是从环境变量读取。

给智能体的指令

将此指令粘贴到智能体的聊天或项目规则中。它帮助智能体了解服务、定价和参考文档,然后使用 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

身份验证

从服务器发送 Authorization: Bearer <API_KEY>。在 Account → API 密钥中创建用于线上数据的密钥,仅授权所需项目和操作。目前不提供公开沙盒。

密钥可允许只读访问、指定操作、单个项目或服务,以及指定 IP/CIDR 范围。到期、暂停和撤销也影响访问。轮换密钥时,请创建具有适当限制的替代密钥,切换集成后再撤销旧密钥。切勿将密钥嵌入浏览器代码、移动应用或 URL。

权限范围用途
watchesMonitoring
events事件与付款
invoicesPayments 账单
webhooksWebhook 端点
streamSSE

各项限制同时适用,不能相互替代。401 表示密钥未被接受;403 可能表示权限不足或 IP 不被允许。无权访问的资源可能返回 404。参见错误与重试。

SDK:即用型客户端

下载所用语言的文件,保存到应用服务器,并将密钥传入构造函数。每个客户端均覆盖本参考中的全部 16 个方法。无需安装 npm、PyPI、Composer 或 crates.io 软件包;下载的文件就是 SDK。

语言要求文件
Node.jsNode.js 18+cryptanio.mjs
PythonPython 3.10+cryptanio.py
PHPPHP 8.1+ / cURLCryptanio.php
GoGo 1.22+cryptanio.go
RustRust 2021 / reqwest 0.12 / serde_json 1cryptanio.rs

这些文件属于同一版本 1.0.0 62。清单包含版本、合约和各文件的 SHA-256。请先在环境中设置 CRYPTANIO_API_KEY 和 CRYPTANIO_PROJECT_ID。这些示例仅读取事件,但访问的是线上账户数据。

Node.js

将示例保存为 .mjs 文件,与 cryptanio.mjs 放在一起。

Node.js
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。通过自己的界面将结果交给付款人,不要写入公开日志。金额保持字符串格式,缺少的字段会被省略。

Node.js
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 放在一起。

Python
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。通过自己的界面将结果交给付款人,不要写入公开日志。金额保持字符串格式,缺少的字段会被省略。

Python
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
<?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
<?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;请将导入路径替换为您自己的模块名称。无需外部依赖。

Go
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。通过自己的界面将结果交给付款人,不要写入公开日志。金额保持字符串格式,缺少的字段会被省略。

Go
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。这是同步阻塞客户端:不要直接在异步工作线程中运行。

Cargo.toml
[dependencies]
reqwest = { version = "=0.12.24", default-features = false, features = ["blocking", "json", "rustls-tls"] }
serde_json = "=1.0.145"
Rust
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。通过自己的界面将结果交给付款人,不要写入公开日志。金额保持字符串格式,缺少的字段会被省略。

Rust
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
createWatchPOST /v1/watches
listWatchesGET /v1/watches
getWatchGET /v1/watches/{id}
deleteWatchDELETE /v1/watches/{id}
listEventsGET /v1/events
listPaymentsGET /v1/payments
createInvoicePOST /v1/invoices
listInvoicesGET /v1/invoices
getInvoiceGET /v1/invoices/{id}
cancelInvoicePOST /v1/invoices/{id}/cancel
createEndpointPOST /v1/endpoints
listEndpointsGET /v1/endpoints
deleteEndpointDELETE /v1/endpoints/{id}
streamEventsGET /v1/stream
getHealthGET /v1/health
listChainsGET /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: ApiErrorstatus, code, message, retry_after
PHP: CryptanioErrorstatus, apiCode, getMessage(), retryAfter
Go: *APIErrorStatus, Code, Message, RetryAfter
Rust: ApiErrorstatus: 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。将示例地址替换为您自己的地址。此请求会关联真实监控,不会模拟交易。

Shell / cURL
#!/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 必须唯一;重试同一操作时复用它。账单创建于线上数据中。

Shell / cURL
#!/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

字段类型必填说明
idstring是您系统中的客户标识符。
maxLength: 120
Product

字段类型必填说明
idstring否您系统中的产品标识符。
maxLength: 120
namestring否产品名称。
maxLength: 200
quantityinteger否数量。
minimum: 1; maximum: 1000000
Payer

字段类型必填说明
commentstring是付款人留言。
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_idstring是不透明标识符;请保持为字符串。
minLength: 1
event_typestring是事件类型;可能添加新类型。
chainstring是网络代码。
servicestring否已知时的服务。
watch_idstring否关联监控时的项目监控 ID。
minLength: 1
occurred_atstring是RFC 3339 格式的 UTC 日期和时间。
date-time
paymentobject | null是付款文档;非付款事件为 null。GET /events 不返回 contract/notice 载荷。
sandboxboolean否测试事件;省略时为 false。
EventPayment

REST/SSE 付款文档。字段取决于事件和网络;不是标准化的 webhook v1 结构。

字段类型必填说明
tx_idstring否交易标识符。
indexinteger否交易内的转账索引。
minimum: 0
heightinteger否可确定时的区块高度。
minimum: 0
addressstring否被观察的地址。
assetstring否REST/SSE asset:字符串标识符,不是 webhook 中的 asset 对象。
amount_rawstring否以字符串表示的最小单位整数金额。
pattern: ^[0-9]+$
amountstring否精确十进制金额字符串;不要使用浮点数。
pattern: ^[0-9]+(?:\.[0-9]+)?$
decimalsinteger否小数位数。
minimum: 0
confirmationsinteger否确认数。
minimum: 0
revisioninteger否转账状态修订号。
minimum: 0
directionstring否相对于被观察地址的方向。
in、out
simulatedboolean否模拟事件。
lateboolean否延迟检测到的转账。
block_hashstring否区块哈希。
match_keystring否网络使用时的匹配标识符。
metaobject否
invoiceobject否
customerobject否
productobject否
payerobject否
metadataobject否

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_changedcontract
asset.unexpectedcontract;该资产不会计为已接受的付款
account.notice / account.notification / treasury.alarm / subscription.noticenotice.subject, notice.text
webhook.test不含付款的控制事件

schema_version: v1 根据事件类型承载 payment、contract 或 notice;webhook.test 不含这些内容。ID 和金额均为字符串。amount_raw 表示最小单位;amount 为十进制表示。通过 chains 检查网络和资产。完整对象结构见 OpenAPI。

请求头含义
X-Timestamp签名时的 Unix 秒数
X-Signaturehex 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 会停用目标及其积压消息。

Webhook 结构示例(示意)
{
  "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_versionstring是Webhook 载荷版本。
v1
event_idstring是事件 ID:投递重试时不变。不同状态可能具有不同的 event_id 值。
minLength: 1
event_typestring是已签名正文中的类型;验证签名后再使用。可能添加新类型。
occurred_atstring是RFC 3339 格式的 UTC 日期和时间。
date-time
project_idstring是不透明标识符;请保持为字符串。
minLength: 1
servicestring否适用时的项目服务。
modestring是事件模式。
live、test
sandboxboolean是明确的模拟标志。
paymentobject否
contractobject否
invoiceobject否
customerobject否
productobject否
payerobject否
metadataobject否
noticeobject否
WebhookPayment

字段类型必填说明
chainstring是网络代码。
directionstring是转入付款:in;转出扣款:out。
in、out
assetobject是
addressstring是被观察的地址。
tx_idstring是交易 ID。
indexinteger是转账索引;不要假设任一网络均为 0。
minimum: 0
heightinteger否已知时的区块高度。
minimum: 0
block_hashstring否已知时的区块哈希。
match_keystring否适用时的网络匹配标识符。
amountstring是精确十进制金额字符串;不要使用浮点数。
pattern: ^[0-9]+(?:\.[0-9]+)?$
amount_rawstring是以字符串表示的最小单位整数金额。
pattern: ^[0-9]+$
decimalsinteger是小数位数。
minimum: 0
confirmationsinteger是确认数。
minimum: 0
revisioninteger是转账状态修订号。不要按交易标识合并不同的 event_id 值。
minimum: 0
lateboolean否延迟检测到的转账;不等同于账单的 late 状态。
simulatedboolean否模拟转账。
valuationobject否
WebhookAsset

字段类型必填说明
symbolstring否已知时的符号;不足以确定代币身份。
standardstring否已知时的代币标准。
contractstring否适用时,代币在其网络中的合约。
identifierstring否其他网络专用的代币标识符。
WebhookContract

字段类型必填说明
chainstring是网络代码。
addressstring是被观察的地址或合约。
tx_idstring是交易 ID。
indexinteger是事件索引。
minimum: 0
heightinteger否区块高度。
minimum: 0
block_hashstring否区块哈希。
kindstring否变更类型。
assetstring否受影响的资产标识(如已知)。
detailobject否
simulatedboolean否模拟事件。
WebhookInvoice

字段类型必填说明
idstring是不透明标识符;请保持为字符串。
minLength: 1
referencestring否订单参考编号。
statusstring否pending 等待付款;seen 已检测;partial 部分付款;paid 已结清;underpaid/overpaid/late 需要作出处理决定;expired 已过期限;canceled 已作废。
pending、seen、paid、underpaid、partial、overpaid、expired、late、canceled
currencystring否订单报价币种。
amountstring否精确十进制金额字符串;不要使用浮点数。
pattern: ^[0-9]+(?:\.[0-9]+)?$
Notice

字段类型必填说明
subjectstring是通知主题。
textstring是通知文本。
Valuation

字段类型必填说明
currencystring是估值币种,例如 USD。
amountstring是精确十进制金额字符串;不要使用浮点数。
pattern: ^[0-9]+(?:\.[0-9]+)?$
ratestring是以十进制字符串表示的资产汇率。
pattern: ^[0-9]+(?:\.[0-9]+)?$
rate_atstring是RFC 3339 格式的 UTC 日期和时间。
date-time
providerstring否已知时的报价来源名称。

验证签名

在解析 JSON 前,对原始正文的全部字节验证 HMAC,包括空格和换行。时效检查限制重放,但不能替代持久化的 event_id 去重。示例仅接受 64 字符的十六进制签名,以及与服务器时间相差不超过 300 秒的时间戳。请使用对应 webhook 目标的密钥。

Node.js
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;
}

下载 Node.js 示例

Go
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
}

下载 Go 示例

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_idstring是不透明标识符;请保持为字符串。
minLength: 1
event_typestring是事件类型;可能添加新类型。
chainstring是网络代码。
servicestring否已知时的服务。
watch_idstring否关联监控时的项目监控 ID。
minLength: 1
occurred_atstring是RFC 3339 格式的 UTC 日期和时间。
date-time
paymentobject | null否付款文档;非付款事件为 null。GET /events 不返回 contract/notice 载荷。
sandboxboolean否测试事件;省略时为 false。
project_idstring否不透明标识符;请保持为字符串。
minLength: 1
project_namestring否项目名称。
urlstring否指向 Account 事件的链接。
uri
contractobject否
modestring否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 并不无条件保证未来不会变化。

错误与安全重试

application/json
{"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、密钥模式和访问权限;限制可能使资源不可见。
402Monitoring 监控数量限制;请检查套餐。
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 合约。

下载 OpenAPI 3.1

API 变更

版本 1.0.0 62 提供可下载的 Node.js、Python、PHP、Go 和 Rust SDK,以及面向 AI 智能体的本地 MCP。参考文档、Markdown 和原始 HTTP 示例仍独立提供。不提供公开沙盒。API 行为不变。

在 v1 内,客户端应容忍新增响应字段。接受未知事件类型不代表履行订单或计入付款。使用变更前,请根据所选环境的版本检查其可用性。