Documentation de l’API

Connectez Cryptanio à votre application.

Contrat v1 / 1.0.0 62 / 2026-10-10

Sommaire

Commencer

L’API est destinée à votre serveur. Configurez d’abord projets, services et pools d’adresses dans Account ; l’API y réalise les opérations autorisées par la clé.

  1. Vérifiez votre compte et activez le service nécessaire dans Mes services.
  2. Créez une clé API avec les permissions minimales et conservez-la sur votre serveur.
  3. Choisissez votre tâche : surveiller une adresse, créer une facture ou recevoir des événements.

Les exemples réalisent des opérations réelles sur les données actives. La création ou la suppression d’objets peut consommer des quotas de service. Les clés ne sont jamais intégrées aux exemples ; elles sont lues depuis les variables d’environnement.

Instructions pour votre agent

Collez cette instruction dans le chat de votre agent ou les règles du projet. Elle aide l’agent à connaître les services, tarifs et références, puis à créer une intégration applicative via l’API REST ou le SDK. L’agent peut répondre dans votre langue.

Instructions d’intégration
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.

Ce bloc ne contient ni clés ni données de compte. Utilisez une clé API avec les permissions minimales pour votre intégration et conservez-la sur votre serveur applicatif. Ne partagez pas la clé dans le chat de l’agent.

Référence de l’API

Méthodes : 16

Authentification

Envoyez Authorization: Bearer <API_KEY> depuis votre serveur. Créez une clé pour les données réelles dans Account → Clés API, limitée aux projets et opérations nécessaires. Aucune sandbox publique n’est actuellement proposée.

Une clé peut autoriser un accès en lecture seule, certaines opérations, un projet ou service et certaines plages IP/CIDR. L’expiration, la suspension et la révocation affectent également l’accès. Pour renouveler une clé, créez sa remplaçante avec les restrictions appropriées, adaptez votre intégration, puis révoquez l’ancienne clé. N’intégrez jamais de clés dans le code du navigateur, les applications mobiles ou les URL.

PérimètreFinalité
watchesSurveillance
eventsÉvénements et paiements
invoicesFactures Payments
webhooksEndpoints webhook
streamSSE

Les restrictions s’appliquent ensemble et ne se remplacent pas. 401 signifie que la clé n’a pas été acceptée ; 403 peut indiquer des permissions insuffisantes ou une IP interdite. Une ressource inaccessible peut renvoyer 404. Voir erreurs et nouvelles tentatives.

SDK : clients prêts à l’emploi

Téléchargez le fichier de votre langage, enregistrez-le sur votre serveur applicatif et transmettez votre clé à son constructeur. Chaque client couvre les 16 méthodes de cette référence. Aucun paquet npm, PyPI, Composer ou crates.io n’est à installer : les fichiers téléchargés constituent le SDK.

LangueExigencesFichier
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

Les fichiers partagent la version 1.0.0 62. Le manifeste contient la version, le contrat et le SHA-256 de chaque fichier. Définissez d’abord CRYPTANIO_API_KEY et CRYPTANIO_PROJECT_ID dans votre environnement. Ces exemples lisent uniquement des événements, mais accèdent aux données réelles du compte.

Node.js

Enregistrez l’exemple dans un fichier .mjs à côté de 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);

Pour lire les instructions de paiement d’une facture existante, indiquez son ID dans CRYPTANIO_RESOURCE_ID. L’exemple conserve le payment_route obligatoire, le payment_uri renvoyé pour le QR et le checkout_url exact. Transmettez le résultat au payeur dans votre interface, pas dans des journaux publics. Les montants restent des chaînes et les champs absents sont omis.

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

Enregistrez l’exemple dans un fichier .py à côté de 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)

Pour lire les instructions de paiement d’une facture existante, indiquez son ID dans CRYPTANIO_RESOURCE_ID. L’exemple conserve le payment_route obligatoire, le payment_uri renvoyé pour le QR et le checkout_url exact. Transmettez le résultat au payeur dans votre interface, pas dans des journaux publics. Les montants restent des chaînes et les champs absents sont omis.

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

Enregistrez l’exemple dans un fichier .php à côté de Cryptanio.php ; activez l’extension 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);

Pour lire les instructions de paiement d’une facture existante, indiquez son ID dans CRYPTANIO_RESOURCE_ID. L’exemple conserve le payment_route obligatoire, le payment_uri renvoyé pour le QR et le checkout_url exact. Transmettez le résultat au payeur dans votre interface, pas dans des journaux publics. Les montants restent des chaînes et les champs absents sont omis.

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

Placez le fichier dans un répertoire cryptanio/ de votre module Go. Cet exemple utilise le module example.local/myapp ; remplacez le chemin d’import par le nom de votre propre module. Aucune dépendance externe n’est nécessaire.

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

Pour lire les instructions de paiement d’une facture existante, indiquez son ID dans CRYPTANIO_RESOURCE_ID. L’exemple conserve le payment_route obligatoire, le payment_uri renvoyé pour le QR et le checkout_url exact. Transmettez le résultat au payeur dans votre interface, pas dans des journaux publics. Les montants restent des chaînes et les champs absents sont omis.

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

Enregistrez le fichier sous src/cryptanio.rs et l’exemple sous src/main.rs. Ajoutez les dépendances ci-dessous à Cargo.toml. Il s’agit d’un client synchrone bloquant : ne l’exécutez pas directement sur un worker asynchrone.

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

Pour lire les instructions de paiement d’une facture existante, indiquez son ID dans CRYPTANIO_RESOURCE_ID. L’exemple conserve le payment_route obligatoire, le payment_uri renvoyé pour le QR et le checkout_url exact. Transmettez le résultat au payeur dans votre interface, pas dans des journaux publics. Les montants restent des chaînes et les champs absents sont omis.

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

Appel des méthodes

Node.js et PHP utilisent listEvents, Python et Rust utilisent list_events, Go utilise ListEvents. Chaque méthode correspond à une requête dans la référence. Les filtres sont un objet/map, ou une liste de paires en Rust. Les méthodes Go prennent toujours context.Context en premier. Les filtres inconnus sont rejetés avant l’envoi d’une requête.

Méthode 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 création de surveillances et webhooks prend un corps de requête. Les méthodes de ressource unique prennent son ID sous forme de chaîne. createInvoice(body, idempotencyKey) exige une clé de 16 à 80 caractères composée de lettres ASCII, chiffres, tirets ou traits de soulignement. Réutilisez la même clé et le même corps lors d’une nouvelle tentative ; le SDK ne génère pas de nouvelle clé et ne réessaie pas automatiquement. Consultez chaque page de méthode pour les corps, erreurs et permissions.

Réponses, erreurs et streaming

Les réponses JSON conservent leurs noms de champs ; les ID, montants et curseurs restent des chaînes. La pagination est manuelle, conformément à la documentation de chaque méthode. Les erreurs exposent le statut HTTP, code, message et Retry-After. Sans réponse HTTP, status vaut 0 (Rust utilise None). Les redirections ne sont pas suivies. Les réponses JSON ordinaires sont limitées à 8 MiB. Le délai d’attente par défaut est de 30 secondes, configurable jusqu’à 300 secondes. En Python, il s’applique à une opération réseau ; dans les autres clients, il couvre la requête entière, y compris le streaming. Un délai d’attente dépassé ne prouve pas qu’une écriture n’a pas été exécutée.

Langage / erreurChamps
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 expose les octets SSE bruts sans reconnexion ni analyse d’événements : un ReadableStream Node.js, une réponse HTTP Python pouvant être fermée, Go io.ReadCloser ou Rust blocking::Response. Libérez le flux lorsqu’il n’est plus nécessaire. PHP transmet des fragments à une fonction de rappel ; renvoyez false pour arrêter. Le streaming ne remplace pas les webhooks et ne garantit pas la relecture de l’historique ; voir les règles SSE.

Votre première surveillance

Dans Account, créez un projet avec le service Monitoring actif et une clé autorisant les surveillances et événements. Effectuez la vérification du compte si nécessaire. Copiez l’ID du projet depuis ses paramètres ; ajoutez uniquement une adresse que vous souhaitez réellement surveiller.

Définissez CRYPTANIO_API_KEY et CRYPTANIO_PROJECT_ID dans l’environnement de votre serveur. Remplacez l’adresse d’exemple par la vôtre. Cette requête rattache une surveillance réelle ; elle ne simule pas une transaction.

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

Une réponse réussie contient id : conservez-le. GET /v1/watches/{id} renvoie la surveillance et ses paramètres, pas les paiements. Pour voir l’activité détectée, demandez GET /v1/events filtré par watch_id. Une liste vide avant la détection d’une transaction est normale. Réponse, erreurs et exemples dans d’autres langues.

Factures Payments

Activez Payments pour votre projet et ajoutez les adresses prises en charge à son pool dans Account. Envoyez explicitement project plutôt que de dépendre du projet sélectionné par défaut. amount est une chaîne exprimée en unités de crypto-actif ; invoice_currency/invoice_amount décrivent séparément le prix indiqué pour votre commande.

Définissez CRYPTANIO_PROJECT_ID sur votre projet Payments et CRYPTANIO_API_KEY sur une clé autorisant les factures. CRYPTANIO_OPERATION_ID doit être unique pour une nouvelle commande ; réutilisez-le lors d’une nouvelle tentative de la même opération. La facture est créée dans les données réelles.

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

Votre clé doit autoriser invoices et Payments ; la clé de l’exemple Monitoring peut être limitée à un autre service. Idempotency-Key contient 16-80 lettres ASCII, chiffres, - et _. Répéter la même requête normalisée avec la même clé renvoie la facture existante avec 201 ; un contenu modifié renvoie 409 conflict. Les clés d’opération couvrent tout le compte. reference ne les remplace pas.

Montrez à l’acheteur le réseau, l’actif, l’adresse, amount et l’échéance renvoyés. Si payment_route est présent, transmettez son kind et sa valeur value exacte sous forme de chaîne : destination_tag, memo, comment ou reference est nécessaire pour attribuer le paiement à cette facture. Le champ reference de la commande est distinct.

Utilisez le payment_uri renvoyé pour le QR de paiement lorsqu’il est présent. Une référence Solana Pay n’est pas un memo du portefeuille : ne remplacez pas l’URI par une simple adresse et ne le reconstruisez pas. Si le portefeuille ne peut pas inclure l’identifiant requis, proposez au payeur checkout_url ou un portefeuille compatible.

checkout_url est la page de paiement hébergée des nouvelles factures. Partagez uniquement l’URL exacte renvoyée avec le payeur ; ne la construisez pas à partir de l’ID. Le lien révèle les détails et l’état du paiement : ne le publiez pas dans des journaux publics. S’il est absent, affichez les instructions complètes. Une redirection du navigateur ne confirme pas le paiement : vérifiez l’API ou un webhook authentifié.

asked peut différer de amount pour distinguer les commandes. Effectuez le rapprochement avec amount_raw, un entier de précision arbitraire. Le TTL par défaut est de 60 minutes, avec un maximum de 10080 minutes. Les listes de factures acceptent before sans renvoyer next_before : continuez avec l’ID du dernier élément et arrêtez-vous à une page vide.

Customer

ChampTypeObligatoireDescription
idstringOuiIdentifiant du client dans votre système.
maxLength: 120
Product

ChampTypeObligatoireDescription
idstringNonIdentifiant du produit dans votre système.
maxLength: 120
namestringNonNom du produit.
maxLength: 200
quantityintegerNonQuantité.
minimum: 1 ; maximum: 1000000
Payer

ChampTypeObligatoireDescription
commentstringOuiCommentaire du payeur.
maxLength: 500
InvoiceMetadata

Jusqu’à 20 paires, au maximum 8192 octets JSON. N’incluez pas de secrets.

object Jusqu’à 20 paires, au maximum 8192 octets JSON. N’incluez pas de secrets.

Surveillances Monitoring

Une surveillance appartient à un seul projet Monitoring. Une même adresse peut être utilisée dans plusieurs projets avec des règles indépendantes de direction et d’actifs. Connectez les listes du carnet d’adresses et les sources d’adresses dans Account ; cette API REST n’expose pas leurs commandes de gestion.

La liste est triée par ID décroissant. Si next_before est présent, transmettez-le comme before dans la requête suivante, en conservant le projet et les filtres. limit vaut 50 par défaut et est plafonné à 200. Stockez les ID sous forme de chaînes. Les surveillances supprimées quittent la liste active ; cette suppression ne retire pas l’adresse des autres projets ni du carnet d’adresses.

Événements et historique

/v1/events renvoie les événements accessibles à la clé dans tous les services ; /v1/payments est limité à Payments. Filtrez par project, address, chain, type, watch_id et tx_id. before est un curseur d’ID, pas une date ; il n’existe pas de filtre from/to. next_before permet de récupérer la page suivante.

L’historique des événements détectés est disponible pendant au maximum 30 jours. Il ne constitue pas une archive de chaque message livré : la récupération des messages notice et webhook.test n’y est pas garantie. REST Event possède son propre schéma, distinct des webhooks ; des champs supplémentaires peuvent être ajoutés dans v1.

Event

ChampTypeObligatoireDescription
event_idstringOuiIdentifiant opaque ; conservez-le sous forme de chaîne.
minLength: 1
event_typestringOuiType d’événement ; de nouveaux types peuvent être ajoutés.
chainstringOuiCode réseau.
servicestringNonService, si connu.
watch_idstringNonID de surveillance du projet, en cas de lien à une surveillance.
minLength: 1
occurred_atstringOuiDate et heure UTC selon RFC 3339.
date-time
paymentobject | nullOuiDocument de paiement, ou null pour un événement sans paiement. GET /events ne renvoie pas de contenus contract/notice.
sandboxbooleanNonÉvénement de test ; l’omission signifie false.
EventPayment

Document de paiement REST/SSE. Les champs dépendent de l’événement et du réseau ; ce n’est pas la structure normalisée webhook v1.

ChampTypeObligatoireDescription
tx_idstringNonIdentifiant de transaction.
indexintegerNonIndex du transfert dans la transaction.
minimum: 0
heightintegerNonHauteur du bloc, lorsqu’elle est connue.
minimum: 0
addressstringNonAdresse observée.
assetstringNonActif REST/SSE : identifiant sous forme de chaîne, pas l’objet asset du webhook.
amount_rawstringNonMontant entier en plus petite unité sous forme de chaîne.
pattern: ^[0-9]+$
amountstringNonMontant décimal exact sous forme de chaîne ; n’utilisez pas de virgule flottante.
pattern: ^[0-9]+(?:\.[0-9]+)?$
decimalsintegerNonNombre de décimales.
minimum: 0
confirmationsintegerNonConfirmations.
minimum: 0
revisionintegerNonRévision de l’état du transfert.
minimum: 0
directionstringNonDirection par rapport à l’adresse observée.
in, out
simulatedbooleanNonÉvénement simulé.
latebooleanNonTransfert détecté tardivement.
block_hashstringNonHash du bloc.
match_keystringNonIdentifiant de correspondance, si le réseau l’utilise.
metaobjectNon
invoiceobjectNon
customerobjectNon
productobjectNon
payerobjectNon
metadataobjectNon

Événements webhook

La livraison a lieu au moins une fois : un événement peut arriver plusieurs fois. Après vérification de la signature, éliminez les doublons par event_id dans votre intégration. Enregistrez le traitement et votre modification métier dans une seule transaction de base de données. Une clé tx_id/address/asset ne remplace pas event_id : elle fusionne des transitions, directions et projets différents.

Lisez event_type et event_id dans le corps signé. Les en-têtes X-Event-Type/X-Event-Id ne sont pas inclus dans le HMAC ; comparez-les au corps si vous les utilisez. Tenez compte de project_id, direction et payment.revision lors de la mise à jour d’un paiement. Ne supposez pas un ordre de livraison ; ignorez sans risque les types inconnus tout en les conservant pour le diagnostic.

FamilleCorps
payment.pending / confirmed / rolled_backPaiement entrant
debit.pending / confirmed / rolled_backPaiement sortant
contract.approval / owner_added / owner_removed / threshold_changed / permissions_changedcontract
asset.unexpectedcontract ; l’actif n’est pas crédité comme paiement accepté
account.notice / account.notification / treasury.alarm / subscription.noticenotice.subject, notice.text
webhook.testÉvénement de contrôle sans paiement

schema_version: v1 contient payment, contract ou notice selon le type d’événement ; webhook.test n’en contient aucun. Les ID et montants sont des chaînes. amount_raw contient les unités minimales ; amount est une représentation décimale. Vérifiez réseaux et actifs via chains. Les schémas complets des objets sont disponibles dans OpenAPI.

En-têteSignification
X-TimestampSecondes Unix au moment de la signature
X-Signaturehex HMAC-SHA256(timestamp + '.' + raw body)
X-Event-Id / X-Event-TypeComparer au corps signé
X-AttemptNuméro de tentative à partir de 1

Renvoyez 2xx après avoir accepté durablement l’événement. Le délai par tentative est de 10 secondes. Les erreurs réseau, 408, 425, 429 et 5xx sont réessayées pendant au maximum 24 heures, avec des délais de 2 secondes à 15 minutes et une variation aléatoire ; Retry-After est respecté. Les redirections ne sont pas suivies. Les autres 3xx/4xx terminent la livraison ; 410 désactive la destination et sa file d’attente.

Exemple de structure webhook (illustratif)
{
  "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

Corps du webhook public signé v1. payment.* et debit.* contiennent payment ; contract.* et asset.unexpected contiennent contract ; webhook.test n’a pas de contenu métier ; account.notice, account.notification, treasury.alarm et subscription.notice contiennent notice. Éliminez les doublons des nouvelles tentatives de livraison par event_id.

ChampTypeObligatoireDescription
schema_versionstringOuiVersion du contenu webhook.
v1
event_idstringOuiID d’événement : inchangé lors des nouvelles tentatives de livraison. Des états différents peuvent avoir des valeurs event_id différentes.
minLength: 1
event_typestringOuiType dans le corps signé ; utilisez-le après vérification de la signature. De nouveaux types peuvent être ajoutés.
occurred_atstringOuiDate et heure UTC selon RFC 3339.
date-time
project_idstringOuiIdentifiant opaque ; conservez-le sous forme de chaîne.
minLength: 1
servicestringNonService du projet, le cas échéant.
modestringOuiMode de l’événement.
live, test
sandboxbooleanOuiIndicateur explicite de simulation.
paymentobjectNon
contractobjectNon
invoiceobjectNon
customerobjectNon
productobjectNon
payerobjectNon
metadataobjectNon
noticeobjectNon
WebhookPayment

ChampTypeObligatoireDescription
chainstringOuiCode réseau.
directionstringOuiPaiement entrant : in ; débit sortant : out.
in, out
assetobjectOui
addressstringOuiAdresse observée.
tx_idstringOuiID de transaction.
indexintegerOuiIndex du transfert ; ne supposez pas 0 pour un réseau quelconque.
minimum: 0
heightintegerNonHauteur du bloc, si connue.
minimum: 0
block_hashstringNonHash du bloc, si connu.
match_keystringNonIdentifiant de correspondance du réseau, le cas échéant.
amountstringOuiMontant décimal exact sous forme de chaîne ; n’utilisez pas de virgule flottante.
pattern: ^[0-9]+(?:\.[0-9]+)?$
amount_rawstringOuiMontant entier en plus petite unité sous forme de chaîne.
pattern: ^[0-9]+$
decimalsintegerOuiNombre de décimales.
minimum: 0
confirmationsintegerOuiConfirmations.
minimum: 0
revisionintegerOuiRévision de l’état du transfert. Ne fusionnez pas des valeurs event_id distinctes selon l’identité de la transaction.
minimum: 0
latebooleanNonTransfert détecté tardivement ; différent de l’état late d’une facture.
simulatedbooleanNonTransfert simulé.
valuationobjectNon
WebhookAsset

ChampTypeObligatoireDescription
symbolstringNonSymbole, si connu ; insuffisant pour établir l’identité du token.
standardstringNonStandard du token, si connu.
contractstringNonContrat du token dans son réseau, le cas échéant.
identifierstringNonAutre identifiant de token propre au réseau.
WebhookContract

ChampTypeObligatoireDescription
chainstringOuiCode réseau.
addressstringOuiAdresse ou contrat observé.
tx_idstringOuiID de transaction.
indexintegerOuiIndex de l’événement.
minimum: 0
heightintegerNonHauteur du bloc.
minimum: 0
block_hashstringNonHash du bloc.
kindstringNonType de modification.
assetstringNonIdentifiant de l’actif concerné, si connu.
detailobjectNon
simulatedbooleanNonÉvénement simulé.
WebhookInvoice

ChampTypeObligatoireDescription
idstringOuiIdentifiant opaque ; conservez-le sous forme de chaîne.
minLength: 1
referencestringNonRéférence de la commande.
statusstringNonpending attend le paiement ; seen détecté ; partial partiellement payé ; paid réglé ; underpaid/overpaid/late nécessitent une décision ; expired délai dépassé ; canceled annulé.
pending, seen, paid, underpaid, partial, overpaid, expired, late, canceled
currencystringNonDevise du prix indiqué pour la commande.
amountstringNonMontant décimal exact sous forme de chaîne ; n’utilisez pas de virgule flottante.
pattern: ^[0-9]+(?:\.[0-9]+)?$
Notice

ChampTypeObligatoireDescription
subjectstringOuiObjet de la notification.
textstringOuiTexte de la notification.
Valuation

ChampTypeObligatoireDescription
currencystringOuiDevise de valorisation, par exemple USD.
amountstringOuiMontant décimal exact sous forme de chaîne ; n’utilisez pas de virgule flottante.
pattern: ^[0-9]+(?:\.[0-9]+)?$
ratestringOuiTaux de change de l’actif sous forme de chaîne décimale.
pattern: ^[0-9]+(?:\.[0-9]+)?$
rate_atstringOuiDate et heure UTC selon RFC 3339.
date-time
providerstringNonNom de la source de cotation, si connu.

Vérification des signatures

Vérifiez le HMAC sur les octets d’origine du corps avant l’analyse JSON, y compris les espaces et sauts de ligne. La vérification de fraîcheur limite la relecture mais ne remplace pas une déduplication persistante par event_id. Les exemples n’acceptent que les signatures hexadécimales de 64 caractères et les horodatages à moins de 300 secondes de l’heure serveur. Utilisez le secret de la destination webhook concernée.

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

Télécharger l’exemple 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
}

Télécharger l’exemple Go

Endpoints webhook

Ajoutez une URL HTTPS publique sur le port 443 pour un projet précis. Les identifiants dans l’URL, les fragments, les redirections et les destinations non publiques sont interdits. Jusqu’à 20 endpoints sont autorisés par compte. Chacun possède un secret distinct, renvoyé uniquement à sa création ; conservez-le sur votre serveur.

Créer à nouveau la même URL pour le même projet et le même mode renvoie 200 et l’endpoint existant sans son secret. Un nouvel endpoint renvoie 201. DELETE renvoie 200 avec id et deleted: true, y compris lors d’une répétition sûre. Une clé ne voit que son mode et les projets autorisés ; la livraison suit les paramètres du projet.

Flux SSE

SSE sert à l’intégration côté serveur et à l’affichage en direct ; il ne garantit ni la livraison ni la relecture des événements manqués. Utilisez un client HTTP côté serveur avec l’en-tête Bearer. EventSource standard du navigateur ne peut pas envoyer cet en-tête ; ne placez pas votre clé API dans le code du navigateur ni dans une chaîne de requête.

Les messages utilisent event: payment et leur propre structure StreamEvent, pas webhook v1 ni REST Event. Les commentaires commencent par deux-points ; les signaux de maintien de connexion arrivent environ toutes les 20 secondes. retry: 5000 est une indication de reconnexion. Après une déconnexion, récupérez l’historique disponible des événements blockchain via events.

StreamEvent

JSON dans les données SSE ; différent de webhook v1. Les événements sans paiement omettent payment et peuvent inclure contract.

ChampTypeObligatoireDescription
event_idstringOuiIdentifiant opaque ; conservez-le sous forme de chaîne.
minLength: 1
event_typestringOuiType d’événement ; de nouveaux types peuvent être ajoutés.
chainstringOuiCode réseau.
servicestringNonService, si connu.
watch_idstringNonID de surveillance du projet, en cas de lien à une surveillance.
minLength: 1
occurred_atstringOuiDate et heure UTC selon RFC 3339.
date-time
paymentobject | nullNonDocument de paiement, ou null pour un événement sans paiement. GET /events ne renvoie pas de contenus contract/notice.
sandboxbooleanNonÉvénement de test ; l’omission signifie false.
project_idstringNonIdentifiant opaque ; conservez-le sous forme de chaîne.
minLength: 1
project_namestringNonNom du projet.
urlstringNonLien vers l’événement Account.
uri
contractobjectNon
modestringNonlive ou test.
live, test

États des paiements et factures

L’état d’un paiement blockchain et celui d’une commande sont différents. payment.pending indique une détection avant confirmation suffisante ; payment.confirmed signifie que la règle de confirmation du réseau/projet est atteinte ; payment.rolled_back signale une annulation. N’exécutez pas une commande sur la base d’événements pending, de débits sortants ou d’événements simulés.

InvoiceSignification
pendingEn attente de paiement
partialPartie reçue ; régie par la politique de paiement partiel du projet
paidPaiement accepté
underpaidUn sous-paiement nécessite une décision
overpaidUn trop-perçu nécessite une décision selon la politique applicable
lateUn paiement après expiration nécessite une décision
expiredDélai de paiement expiré
canceledFacture annulée ; l’enregistrement reste consultable

needs_decision est présent pour underpaid, overpaid et late. Résolvez ces états dans Account. Une réorganisation et une réinclusion peuvent produire de nouvelles transitions et un nouveau payment.revision ; confirmed n’est pas une garantie inconditionnelle contre les changements futurs.

Erreurs et nouvelles tentatives sûres

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

Utilisez le statut HTTP et error.code pour la logique, et message pour le diagnostic. Les listes réussies contiennent data ; un objet unique est renvoyé directement. Health utilise son propre schéma status/version, y compris sur 503. Ne dépendez pas de la formulation exacte de message.

HTTPAction
400Corrigez le JSON, les formats et les paramètres ; ne réessayez pas sans modification
401Vérifiez la clé, son expiration, sa suspension et sa révocation
403Vérifiez les périmètres, la lecture/écriture, le projet, le service, l’IP et la vérification du compte
404Vérifiez l’ID, le mode de la clé et l’accès ; les restrictions peuvent masquer la ressource
402Limite de surveillances Monitoring ; vérifiez votre offre
409Conflit, service inactif, pool occupé ou limite ; examinez le code
429Respectez Retry-After ; réduisez le débit global des requêtes du compte
500 / 503Réessayez avec un délai progressif limité ; création de facture uniquement avec l’Idempotency-Key d’origine

Un délai d’attente dépassé ne signifie pas que l’opération n’a pas été exécutée. Ne changez pas la clé d’opération d’une facture lors d’une nouvelle tentative. Pour les méthodes sans protection contre les doublons, lisez d’abord l’état ; ne supposez pas que chaque POST est idempotent.

Réseaux et limites

Utilisez chains sur l’hôte API sélectionné pour son catalogue de réseaux, actifs, capacités et confirmations. La présence dans le catalogue général ne garantit pas qu’un réseau soit activé dans cet environnement ou prenne en charge toutes les opérations. Vérifiez les quotas de service et le budget dans Account ; les prix figurent sur la page Tarifs.

Le quota REST authentifié du compte est de 10 requêtes/s sans service payant et de 50 requêtes/s avec un service payant ; la capacité de rafale courte est de 20/100. Toutes les clés et tous les modes du compte partagent ce quota. Le dépassement renvoie 429 avec Retry-After. Les quotas de service et les limites de débit API sont des restrictions distinctes.

Tarifs et quotas des services

Versions de l’API

Cette référence décrit v1 dans la version 1.0.0 62 ; mise à jour le 2026-10-10. Avant d’utiliser de nouvelles capacités, vérifiez la version déployée via GET /v1/health. La version de la documentation ne confirme pas le déploiement.

API publiqueContrat
https://api.cryptanio.com/v1

Utilisez /v1 pour des intégrations stables. Un chemin sans version sélectionne le contrat stable actuel de l’environnement ; un /vN inconnu renvoie 404. X-API-Version identifie le contrat ; X-Server-Version et health.version identifient la version du produit, pas un autre contrat API.

Télécharger OpenAPI 3.1

Modifications de l’API

La version 1.0.0 62 inclut des SDK téléchargeables pour Node.js, Python, PHP, Go et Rust ainsi que MCP local pour les agents IA. La référence, le Markdown et les exemples HTTP bruts restent disponibles séparément. Aucune sandbox publique n’est proposée. Le comportement de l’API est inchangé.

Dans v1, les clients doivent tolérer les champs de réponse supplémentaires. Accepter un type d’événement inconnu ne signifie pas exécuter une commande ou créditer un paiement. Vérifiez la disponibilité d’une modification dans la version de l’environnement sélectionné avant utilisation.