Documentación de la API

Conecta Cryptanio a tu aplicación.

Contrato v1 / 1.0.0 62 / 2026-10-10

Contenido

Comenzar

La API es para tu servidor. Configura primero proyectos, servicios y pools de direcciones en Account; la API realiza dentro de ellos las operaciones autorizadas por la clave.

  1. Verifica tu cuenta y activa el servicio necesario en Mis servicios.
  2. Crea una clave API con los permisos mínimos y guárdala en tu servidor.
  3. Elige tu tarea: vigilar una dirección, crear una factura o recibir eventos.

Los ejemplos realizan operaciones reales sobre datos activos. Crear o eliminar objetos puede consumir cuotas de servicio. Las claves nunca se incluyen en los ejemplos; se leen de variables de entorno.

Instrucciones para tu agente

Pega esta instrucción en el chat de tu agente o en las reglas del proyecto. Ayuda al agente a conocer los servicios, precios y referencia, y después a escribir una integración de la aplicación con la API REST o el SDK. El agente puede responder en tu idioma.

Instrucciones de integración
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.

Este bloque no contiene claves ni datos de cuenta. Utiliza una clave API con los permisos mínimos para tu integración y guárdala en el servidor de tu aplicación. No compartas la clave en el chat del agente.

Referencia de la API

Métodos: 16

Autenticación

Envía Authorization: Bearer <API_KEY> desde tu servidor. Crea una clave para datos reales en Account → Claves API, limitada a los proyectos y operaciones necesarios. Actualmente no se ofrece un entorno de pruebas público.

Una clave puede permitir acceso de solo lectura, operaciones concretas, un proyecto o servicio y determinados rangos IP/CIDR. La caducidad, la pausa y la revocación también afectan al acceso. Para rotar una clave, crea su sustituta con las restricciones adecuadas, actualiza tu integración y revoca la anterior. Nunca incluyas claves en código del navegador, aplicaciones móviles ni URL.

ÁmbitoFinalidad
watchesMonitorización
eventsEventos y pagos
invoicesFacturas de Payments
webhooksEndpoints de webhook
streamSSE

Las restricciones se aplican conjuntamente, no se sustituyen entre sí. 401 significa que la clave no fue aceptada; 403 puede indicar permisos insuficientes o una IP no permitida. Un recurso inaccesible puede devolver 404. Consulta errores y reintentos.

SDK: clientes listos para usar

Descarga el archivo de tu lenguaje, guárdalo en el servidor de tu aplicación y pasa tu clave a su constructor. Cada cliente cubre los 16 métodos de esta referencia. No hay ningún paquete de npm, PyPI, Composer ni crates.io que instalar: los archivos descargados son el SDK.

IdiomaRequisitosArchivo
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

Los archivos comparten la versión 1.0.0 62. El manifiesto contiene la versión, el contrato y el SHA-256 de cada archivo. Configura primero CRYPTANIO_API_KEY y CRYPTANIO_PROJECT_ID en tu entorno. Estos ejemplos solo leen eventos, pero acceden a datos reales de la cuenta.

Node.js

Guarda el ejemplo como archivo .mjs junto a 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);

Para leer las instrucciones de pago de una factura existente, indica su ID en CRYPTANIO_RESOURCE_ID. El ejemplo conserva el payment_route obligatorio, el payment_uri devuelto para el QR y el checkout_url exacto. Entrega el resultado al pagador mediante tu interfaz; no lo publiques en registros públicos. Los importes siguen siendo cadenas y se omiten los campos ausentes.

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

Guarda el ejemplo como archivo .py junto a 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)

Para leer las instrucciones de pago de una factura existente, indica su ID en CRYPTANIO_RESOURCE_ID. El ejemplo conserva el payment_route obligatorio, el payment_uri devuelto para el QR y el checkout_url exacto. Entrega el resultado al pagador mediante tu interfaz; no lo publiques en registros públicos. Los importes siguen siendo cadenas y se omiten los campos ausentes.

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

Guarda el ejemplo como archivo .php junto a Cryptanio.php; activa la extensión 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);

Para leer las instrucciones de pago de una factura existente, indica su ID en CRYPTANIO_RESOURCE_ID. El ejemplo conserva el payment_route obligatorio, el payment_uri devuelto para el QR y el checkout_url exacto. Entrega el resultado al pagador mediante tu interfaz; no lo publiques en registros públicos. Los importes siguen siendo cadenas y se omiten los campos ausentes.

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

Coloca el archivo en un directorio cryptanio/ dentro de tu módulo Go. Este ejemplo utiliza el módulo example.local/myapp; sustituye la ruta de importación por el nombre de tu módulo. No se necesitan dependencias externas.

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

Para leer las instrucciones de pago de una factura existente, indica su ID en CRYPTANIO_RESOURCE_ID. El ejemplo conserva el payment_route obligatorio, el payment_uri devuelto para el QR y el checkout_url exacto. Entrega el resultado al pagador mediante tu interfaz; no lo publiques en registros públicos. Los importes siguen siendo cadenas y se omiten los campos ausentes.

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

Guarda el archivo como src/cryptanio.rs y el ejemplo como src/main.rs. Añade las dependencias siguientes a Cargo.toml. Es un cliente síncrono bloqueante: no lo ejecutes directamente en un worker asíncrono.

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(())
}

Para leer las instrucciones de pago de una factura existente, indica su ID en CRYPTANIO_RESOURCE_ID. El ejemplo conserva el payment_route obligatorio, el payment_uri devuelto para el QR y el checkout_url exacto. Entrega el resultado al pagador mediante tu interfaz; no lo publiques en registros públicos. Los importes siguen siendo cadenas y se omiten los campos ausentes.

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(())
}

Llamada a métodos

Node.js y PHP usan listEvents, Python y Rust usan list_events, Go usa ListEvents. Cada método corresponde a una solicitud de la referencia. Los filtros son un objeto/mapa, o una lista de pares en Rust. Los métodos de Go siempre reciben context.Context primero. Los filtros desconocidos se rechazan antes de enviar una solicitud.

Método Node.js / PHPHTTP
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

La creación de vigilancias y webhooks recibe un cuerpo de solicitud. Los métodos de recurso único reciben su ID como cadena. createInvoice(body, idempotencyKey) requiere una clave de 16 a 80 caracteres con letras ASCII, dígitos, guiones o guiones bajos. Reutiliza la misma clave y cuerpo al reintentar; el SDK no genera una nueva clave ni reintenta automáticamente. Consulta la página de cada método para ver cuerpos, errores y permisos.

Respuestas, errores y streaming

Las respuestas JSON conservan los nombres de sus campos; los ID, importes y cursores siguen siendo cadenas. La paginación es manual, como se documenta para cada método. Los errores exponen el estado HTTP, code, message y Retry-After. Sin respuesta HTTP, status es 0 (Rust utiliza None). No se siguen redirecciones. Las respuestas JSON normales se limitan a 8 MiB. El tiempo de espera predeterminado es de 30 segundos, configurable hasta 300 segundos. En Python se aplica a una operación de red; en los demás clientes cubre toda la solicitud, incluido el streaming. Un tiempo de espera agotado no demuestra que una escritura no se haya ejecutado.

Lenguaje / errorCampos
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 expone bytes SSE sin procesar, sin reconexión ni análisis de eventos: un ReadableStream de Node.js, una respuesta HTTP de Python que se puede cerrar, Go io.ReadCloser o Rust blocking::Response. Libera el flujo cuando ya no sea necesario. PHP pasa fragmentos a una función de retorno; devuelve false para detenerlo. El streaming no sustituye los webhooks ni garantiza reproducir el historial; consulta las reglas SSE.

Tu primera vigilancia

En Account, crea un proyecto con el servicio Monitoring activo y una clave con permisos de vigilancias y eventos. Completa la verificación de la cuenta cuando se requiera. Copia el ID del proyecto desde su configuración; añade solo una dirección que realmente quieras vigilar.

Configura CRYPTANIO_API_KEY y CRYPTANIO_PROJECT_ID en el entorno de tu servidor. Sustituye la dirección de ejemplo por la tuya. Esta solicitud vincula una vigilancia real; no simula una transacción.

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

Una respuesta correcta contiene id: guárdalo. GET /v1/watches/{id} devuelve la vigilancia y su configuración, no los pagos. Para ver la actividad detectada, solicita GET /v1/events filtrado por watch_id. Es normal que la lista esté vacía antes de detectar una transacción. Respuesta, errores y ejemplos en otros idiomas.

Facturas de Payments

Activa Payments para tu proyecto y añade direcciones compatibles a su pool en Account. Envía project explícitamente en vez de depender de la selección del proyecto predeterminado. amount es una cadena en unidades del criptoactivo; invoice_currency/invoice_amount describen por separado la cotización del pedido.

Configura CRYPTANIO_PROJECT_ID con tu proyecto Payments y CRYPTANIO_API_KEY con una clave con permiso de facturas. CRYPTANIO_OPERATION_ID debe ser único para un nuevo pedido; reutilízalo al reintentar la misma operación. La factura se crea en datos reales.

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

Tu clave debe permitir invoices y Payments; la clave del ejemplo de Monitoring puede estar restringida a otro servicio. Idempotency-Key contiene 16-80 letras ASCII, dígitos, - y _. Repetir la misma solicitud normalizada con la misma clave devuelve la factura existente con 201; cambiar el contenido devuelve 409 conflict. Las claves de operación abarcan toda la cuenta. reference no las sustituye.

Muestra al comprador la red, el activo, la dirección, amount y el vencimiento devueltos. Si aparece payment_route, transmite su kind y su valor value exacto como cadena: destination_tag, memo, comment o reference es obligatorio para atribuir el pago a esta factura. El campo reference del pedido es independiente.

Usa el payment_uri devuelto para el QR de pago cuando esté presente. Una referencia Solana Pay no es un memo de la cartera: no sustituyas el URI por una dirección simple ni lo reconstruyas. Si la cartera no puede incluir el identificador obligatorio, ofrece al pagador checkout_url o una cartera compatible.

checkout_url es la página de pago alojada para las facturas nuevas. Comparte con el pagador únicamente la URL exacta devuelta; no la construyas a partir del ID. El enlace revela los detalles y el estado del pago, por lo que no debes publicarlo en registros públicos. Si falta, muestra las instrucciones completas. Una redirección del navegador no confirma el pago: comprueba la API o un webhook verificado.

asked puede diferir de amount para distinguir pedidos. Concilia usando amount_raw como entero de precisión arbitraria. El TTL predeterminado es de 60 minutos y el máximo de 10080 minutos. Las listas de facturas aceptan before, pero no devuelven next_before: continúa con el ID del último elemento y detente al recibir una página vacía.

Customer

CampoTipoObligatorioDescripción
idstringSíIdentificador del cliente en tu sistema.
maxLength: 120
Product

CampoTipoObligatorioDescripción
idstringNoIdentificador del producto en tu sistema.
maxLength: 120
namestringNoNombre del producto.
maxLength: 200
quantityintegerNoCantidad.
minimum: 1; maximum: 1000000
Payer

CampoTipoObligatorioDescripción
commentstringSíComentario del pagador.
maxLength: 500
InvoiceMetadata

Hasta 20 pares, máximo 8192 bytes JSON. No incluyas secretos.

object Hasta 20 pares, máximo 8192 bytes JSON. No incluyas secretos.

Vigilancias de Monitoring

Una vigilancia pertenece a un proyecto Monitoring. La misma dirección puede utilizarse en varios proyectos con reglas independientes de dirección y activos. Conecta listas de la libreta de direcciones y fuentes de direcciones en Account; esta API REST no expone sus comandos de gestión.

La lista se ordena por ID descendente. Si aparece next_before, pásalo como before en la siguiente solicitud y conserva el proyecto y los filtros. limit tiene como valor predeterminado 50 y se limita a 200. Guarda los ID como cadenas. Las vigilancias eliminadas dejan la lista activa; eliminarlas aquí no borra la dirección de otros proyectos ni de la libreta de direcciones.

Eventos e historial

/v1/events devuelve eventos disponibles para la clave en todos los servicios; /v1/payments se limita a Payments. Filtra por project, address, chain, type, watch_id y tx_id. before es un cursor de ID, no una fecha; no existe filtro from/to. next_before permite obtener la página siguiente.

El historial de eventos detectados está disponible durante un máximo de 30 días. No es un archivo de todos los mensajes entregados: no se garantiza recuperar mensajes notice y webhook.test mediante él. REST Event tiene su propio esquema, independiente de los webhooks; pueden añadirse campos dentro de v1.

Event

CampoTipoObligatorioDescripción
event_idstringSíIdentificador opaco; consérvalo como cadena.
minLength: 1
event_typestringSíTipo de evento; pueden añadirse nuevos tipos.
chainstringSíCódigo de red.
servicestringNoServicio, si se conoce.
watch_idstringNoID de vigilancia del proyecto, si está vinculado a una vigilancia.
minLength: 1
occurred_atstringSíFecha y hora UTC en RFC 3339.
date-time
paymentobject | nullSíDocumento de pago, o null para un evento sin pago. GET /events no devuelve contenidos contract/notice.
sandboxbooleanNoEvento de prueba; la omisión significa false.
EventPayment

Documento de pago REST/SSE. Los campos dependen del evento y la red; no es la estructura normalizada de webhook v1.

CampoTipoObligatorioDescripción
tx_idstringNoIdentificador de transacción.
indexintegerNoÍndice de transferencia dentro de la transacción.
minimum: 0
heightintegerNoAltura del bloque, cuando se conoce.
minimum: 0
addressstringNoDirección observada.
assetstringNoActivo REST/SSE: identificador de cadena, no el objeto asset del webhook.
amount_rawstringNoImporte entero en la unidad mínima como cadena.
pattern: ^[0-9]+$
amountstringNoImporte decimal exacto como cadena; no uses coma flotante.
pattern: ^[0-9]+(?:\.[0-9]+)?$
decimalsintegerNoDecimales.
minimum: 0
confirmationsintegerNoConfirmaciones.
minimum: 0
revisionintegerNoRevisión del estado de transferencia.
minimum: 0
directionstringNoDirección respecto a la dirección observada.
in, out
simulatedbooleanNoEvento simulado.
latebooleanNoTransferencia detectada tarde.
block_hashstringNoHash del bloque.
match_keystringNoIdentificador de coincidencia, cuando lo utiliza la red.
metaobjectNo
invoiceobjectNo
customerobjectNo
productobjectNo
payerobjectNo
metadataobjectNo

Eventos de webhook

La entrega es al menos una vez: un evento puede llegar varias veces. Tras verificar la firma, elimina duplicados por event_id en tu integración. Guarda el procesamiento y el cambio de negocio en una única transacción de base de datos. Una clave tx_id/address/asset no sustituye a event_id: fusiona transiciones, direcciones y proyectos distintos.

Lee event_type y event_id del cuerpo firmado. Las cabeceras X-Event-Type/X-Event-Id no se incluyen en el HMAC; compáralas con el cuerpo si las utilizas. Ten en cuenta project_id, direction y payment.revision al actualizar un pago. No supongas un orden de entrega; ignora de forma segura los tipos desconocidos y consérvalos para diagnóstico.

FamiliaCuerpo
payment.pending / confirmed / rolled_backPago entrante
debit.pending / confirmed / rolled_backPago saliente
contract.approval / owner_added / owner_removed / threshold_changed / permissions_changedcontract
asset.unexpectedcontract; el activo no se acredita como pago aceptado
account.notice / account.notification / treasury.alarm / subscription.noticenotice.subject, notice.text
webhook.testEvento de control sin pago

schema_version: v1 contiene payment, contract o notice según el tipo de evento; webhook.test no contiene ninguno. Los ID e importes son cadenas. amount_raw contiene unidades mínimas; amount es una representación decimal. Comprueba redes y activos mediante chains. Los esquemas completos de los objetos están disponibles en OpenAPI.

CabeceraSignificado
X-TimestampSegundos Unix en el momento de la firma
X-Signaturehex HMAC-SHA256(timestamp + '.' + raw body)
X-Event-Id / X-Event-TypeComparar con el cuerpo firmado
X-AttemptNúmero de intento a partir de 1

Devuelve 2xx tras aceptar el evento de forma duradera. El tiempo de espera por intento es de 10 segundos. Los fallos de red, 408, 425, 429 y 5xx se reintentan durante un máximo de 24 horas, con esperas de 2 segundos a 15 minutos y variación aleatoria; se respeta Retry-After. No se siguen redirecciones. Otros 3xx/4xx finalizan la entrega; 410 retira el destino y su cola pendiente.

Ejemplo de estructura de webhook (ilustrativo)
{
  "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

Cuerpo público de webhook firmado v1. payment.* y debit.* contienen payment; contract.* y asset.unexpected contienen contract; webhook.test no tiene contenido de negocio; account.notice, account.notification, treasury.alarm y subscription.notice contienen notice. Elimina duplicados de reintentos de entrega por event_id.

CampoTipoObligatorioDescripción
schema_versionstringSíVersión del contenido de webhook.
v1
event_idstringSíID del evento: no cambia en los reintentos de entrega. Los distintos estados pueden tener valores event_id distintos.
minLength: 1
event_typestringSíTipo en el cuerpo firmado; úsalo después de verificar la firma. Pueden añadirse nuevos tipos.
occurred_atstringSíFecha y hora UTC en RFC 3339.
date-time
project_idstringSíIdentificador opaco; consérvalo como cadena.
minLength: 1
servicestringNoServicio del proyecto, cuando corresponda.
modestringSíModo del evento.
live, test
sandboxbooleanSíIndicador explícito de simulación.
paymentobjectNo
contractobjectNo
invoiceobjectNo
customerobjectNo
productobjectNo
payerobjectNo
metadataobjectNo
noticeobjectNo
WebhookPayment

CampoTipoObligatorioDescripción
chainstringSíCódigo de red.
directionstringSíPago entrante: in; débito saliente: out.
in, out
assetobjectSí
addressstringSíDirección observada.
tx_idstringSíID de transacción.
indexintegerSíÍndice de transferencia; no supongas 0 para ninguna red.
minimum: 0
heightintegerNoAltura del bloque, si se conoce.
minimum: 0
block_hashstringNoHash del bloque, si se conoce.
match_keystringNoIdentificador de coincidencia de la red, cuando corresponda.
amountstringSíImporte decimal exacto como cadena; no uses coma flotante.
pattern: ^[0-9]+(?:\.[0-9]+)?$
amount_rawstringSíImporte entero en la unidad mínima como cadena.
pattern: ^[0-9]+$
decimalsintegerSíDecimales.
minimum: 0
confirmationsintegerSíConfirmaciones.
minimum: 0
revisionintegerSíRevisión del estado de transferencia. No agrupes valores event_id distintos por identidad de transacción.
minimum: 0
latebooleanNoTransferencia detectada tarde; no equivale al estado late de una factura.
simulatedbooleanNoTransferencia simulada.
valuationobjectNo
WebhookAsset

CampoTipoObligatorioDescripción
symbolstringNoSímbolo, si se conoce; no basta para establecer la identidad del token.
standardstringNoEstándar del token, si se conoce.
contractstringNoContrato del token en su red, cuando corresponda.
identifierstringNoOtro identificador de token específico de la red.
WebhookContract

CampoTipoObligatorioDescripción
chainstringSíCódigo de red.
addressstringSíDirección o contrato observado.
tx_idstringSíID de transacción.
indexintegerSíÍndice del evento.
minimum: 0
heightintegerNoAltura del bloque.
minimum: 0
block_hashstringNoHash del bloque.
kindstringNoTipo de cambio.
assetstringNoIdentificador del activo afectado, si se conoce.
detailobjectNo
simulatedbooleanNoEvento simulado.
WebhookInvoice

CampoTipoObligatorioDescripción
idstringSíIdentificador opaco; consérvalo como cadena.
minLength: 1
referencestringNoReferencia del pedido.
statusstringNopending espera el pago; seen detectado; partial parcialmente pagado; paid liquidado; underpaid/overpaid/late requieren una decisión; expired plazo vencido; canceled anulado.
pending, seen, paid, underpaid, partial, overpaid, expired, late, canceled
currencystringNoMoneda de la cotización del pedido.
amountstringNoImporte decimal exacto como cadena; no uses coma flotante.
pattern: ^[0-9]+(?:\.[0-9]+)?$
Notice

CampoTipoObligatorioDescripción
subjectstringSíAsunto del aviso.
textstringSíTexto del aviso.
Valuation

CampoTipoObligatorioDescripción
currencystringSíMoneda de valoración, como USD.
amountstringSíImporte decimal exacto como cadena; no uses coma flotante.
pattern: ^[0-9]+(?:\.[0-9]+)?$
ratestringSíTipo de cambio del activo como cadena decimal.
pattern: ^[0-9]+(?:\.[0-9]+)?$
rate_atstringSíFecha y hora UTC en RFC 3339.
date-time
providerstringNoNombre de la fuente de cotización, si se conoce.

Verificación de firmas

Verifica el HMAC sobre los bytes originales del cuerpo antes de analizar el JSON, incluidos espacios y saltos de línea. Comprobar la antigüedad limita la repetición, pero no sustituye la deduplicación persistente por event_id. Los ejemplos solo aceptan firmas hexadecimales de 64 caracteres y marcas de tiempo a menos de 300 segundos de la hora del servidor. Usa el secreto del destino de webhook concreto.

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

Descargar ejemplo de 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
}

Descargar ejemplo de Go

Endpoints de webhook

Añade una URL HTTPS pública en el puerto 443 para un proyecto concreto. No se permiten credenciales en la URL, fragmentos, redirecciones ni destinos no públicos. Se permiten hasta 20 endpoints por cuenta. Cada uno tiene un secreto independiente, que se devuelve solo al crearlo; guárdalo en tu servidor.

Crear otra vez la misma URL para el mismo proyecto y modo devuelve 200 y el endpoint existente sin su secreto. Un endpoint nuevo devuelve 201. DELETE devuelve 200 con id y deleted: true, incluida una repetición segura. Una clave solo ve su modo y los proyectos permitidos; la entrega sigue la configuración del proyecto.

Flujo SSE

SSE sirve para integrar desde el servidor y mostrar datos en directo; no garantiza la entrega ni reproduce eventos perdidos. Usa un cliente HTTP de servidor con la cabecera Bearer. EventSource estándar del navegador no puede enviarla; no incluyas tu clave API en código del navegador ni en parámetros de URL.

Los mensajes usan event: payment y su propia estructura StreamEvent, no webhook v1 ni REST Event. Los comentarios empiezan con dos puntos; las señales de mantenimiento de conexión llegan aproximadamente cada 20 segundos. retry: 5000 es una indicación de reconexión. Tras desconectar, recupera el historial disponible de eventos de blockchain mediante events.

StreamEvent

JSON en los datos SSE; difiere de webhook v1. Los eventos sin pago omiten payment y pueden incluir contract.

CampoTipoObligatorioDescripción
event_idstringSíIdentificador opaco; consérvalo como cadena.
minLength: 1
event_typestringSíTipo de evento; pueden añadirse nuevos tipos.
chainstringSíCódigo de red.
servicestringNoServicio, si se conoce.
watch_idstringNoID de vigilancia del proyecto, si está vinculado a una vigilancia.
minLength: 1
occurred_atstringSíFecha y hora UTC en RFC 3339.
date-time
paymentobject | nullNoDocumento de pago, o null para un evento sin pago. GET /events no devuelve contenidos contract/notice.
sandboxbooleanNoEvento de prueba; la omisión significa false.
project_idstringNoIdentificador opaco; consérvalo como cadena.
minLength: 1
project_namestringNoNombre del proyecto.
urlstringNoEnlace al evento de Account.
uri
contractobjectNo
modestringNolive o test.
live, test

Estados de pagos y facturas

El estado de un pago en blockchain y el de un pedido son diferentes. payment.pending indica la detección antes de tener suficientes confirmaciones; payment.confirmed significa que se alcanzó la regla de confirmación de la red/proyecto; payment.rolled_back informa de una reversión. No entregues un pedido por eventos pending, débitos salientes ni eventos simulados.

InvoiceSignificado
pendingEsperando el pago
partialParte recibida; se rige por la política de pagos parciales del proyecto
paidPago aceptado
underpaidUn pago insuficiente requiere una decisión
overpaidUn pago excesivo requiere una decisión conforme a la política aplicable
lateUn pago tras la caducidad requiere una decisión
expiredPlazo de pago vencido
canceledFactura cancelada; el registro sigue siendo consultable

needs_decision aparece para underpaid, overpaid y late. Resuelve estos estados en Account. Una reorganización y reinclusión pueden producir nuevas transiciones y un nuevo payment.revision; confirmed no es una garantía incondicional contra futuros cambios.

Errores y reintentos seguros

application/json
{"error":{"code":"forbidden","message":"this API key is read-only"}}

Usa el estado HTTP y error.code para la lógica y message para diagnóstico. Las listas correctas tienen data; un objeto único se devuelve directamente. Health utiliza su propio esquema status/version, incluso en 503. No dependas del texto exacto de message.

HTTPAcción
400Corrige el JSON, los formatos y los parámetros; no reintentes sin cambios
401Comprueba la clave, su caducidad, pausa y revocación
403Comprueba los ámbitos, lectura/escritura, proyecto, servicio, IP y verificación de la cuenta
404Comprueba el ID, el modo de la clave y el acceso; las restricciones pueden ocultar el recurso
402Límite de vigilancias de Monitoring; comprueba tu plan
409Conflicto, servicio inactivo, pool ocupado o límite; revisa el código
429Respeta Retry-After; reduce la tasa conjunta de solicitudes de la cuenta
500 / 503Reintenta con espera progresiva limitada; crea facturas solo con la Idempotency-Key original

Un tiempo de espera agotado no significa que la operación no se haya ejecutado. No cambies la clave de operación de una factura al reintentar. En los métodos sin protección contra duplicados, consulta primero el estado; no supongas que todos los POST son idempotentes.

Redes y límites

Utiliza chains en el host API seleccionado para su catálogo de redes, activos, capacidades y confirmaciones. Figurar en el catálogo general no garantiza que una red esté habilitada en ese entorno ni admita todas las operaciones. Comprueba las cuotas de servicio y el presupuesto en Account; los precios están en la página Precios.

La cuota REST autenticada de la cuenta es de 10 solicitudes/s sin servicio de pago y 50 solicitudes/s con un servicio de pago; la capacidad de ráfaga breve es 20/100. Todas las claves y modos de la cuenta comparten esta cuota. Superarla devuelve 429 con Retry-After. Las cuotas de servicio y los límites de tasa de la API son restricciones independientes.

Precios y cuotas de los servicios

Versiones de la API

Esta referencia describe v1 en la versión 1.0.0 62; actualizada el 2026-10-10. Antes de utilizar nuevas funciones, comprueba la versión desplegada mediante GET /v1/health. La versión de la documentación no confirma el despliegue.

API públicaContrato
https://api.cryptanio.com/v1

Utiliza /v1 para integraciones estables. Una ruta sin versión selecciona el contrato estable actual del entorno; un /vN desconocido devuelve 404. X-API-Version identifica el contrato; X-Server-Version y health.version identifican la versión del producto, no un contrato API distinto.

Descargar OpenAPI 3.1

Cambios de la API

La versión 1.0.0 62 incluye SDK descargables para Node.js, Python, PHP, Go y Rust y MCP local para agentes de IA. La referencia, Markdown y los ejemplos HTTP sin procesar siguen disponibles por separado. No se ofrece un entorno de pruebas público. El comportamiento de la API no cambia.

Dentro de v1, los clientes deben tolerar campos de respuesta adicionales. Aceptar un tipo de evento desconocido no significa entregar un pedido ni acreditar un pago. Comprueba la disponibilidad del cambio en la versión del entorno seleccionado antes de usarlo.