Une seule API, un seul parcours hébergé
CryptoPayIn établit le prix d'une commande dans la devise de présentation choisie, verrouille des instantanés vérifiés fiat/USD et crypto/USD, alloue une adresse de dépôt dédiée et surveille ses propres nœuds blockchain en attendant le paiement. La comptabilité interne, les frais et les soldes restent exprimés en USD. Votre backend reçoit immédiatement une URL de paiement, puis les évènements de cycle de vie signés.
Ceci est une API en production. Il n'existe pas de préfixe bac à sable. Chaque création réussie alloue une véritable adresse on-chain. Utilisez un petit montant dans une devise prise en charge pour vos tests de bout en bout et conservez les clés secrètes sur votre serveur.
Comment s'articule l'intégration
Générez un secret une fois depuis Tableau de bord -> Développeurs.
Envoyez en POST la devise de la commande, le montant et l'actif choisi.
Redirigez le client vers l'URL hébergée renvoyée.
Vérifiez le HMAC et mettez à jour votre commande de façon idempotente.
Créez votre premier paiement
Générez une clé API dans le tableau de bord marchand, stockez le secret dans une variable d'environnement, puis créez un paiement depuis votre backend. L'exemple utilise l'ETH afin de pouvoir être testé sans avoir à choisir de réseau de token.
curl --request POST https://cryptopayin.com/v1/payments \
--header "Authorization: Bearer $CPI_SECRET_KEY" \
--header "Content-Type: application/json" \
--header "Idempotency-Key: order_1042" \
--data '{
"amount": 49.99,
"currency": "USD",
"asset": "ETH",
"order_ref": "order_1042",
"redirect_url": "https://shop.example/orders/1042/paid"
}'
const response = await fetch("https://cryptopayin.com/v1/payments", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.CPI_SECRET_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "order_1042"
},
body: JSON.stringify({
amount: 49.99, currency: "USD", asset: "ETH",
order_ref: "order_1042",
redirect_url: "https://shop.example/orders/1042/paid"
})
});
if (!response.ok) throw new Error(await response.text());
const payment = await response.json();
$payload = json_encode([
'amount' => 49.99, 'currency' => 'USD', 'asset' => 'ETH',
'order_ref' => 'order_1042',
'redirect_url' => 'https://shop.example/orders/1042/paid',
]);
$ch = curl_init('https://cryptopayin.com/v1/payments');
curl_setopt_array($ch, [
CURLOPT_POST => true, CURLOPT_POSTFIELDS => $payload,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CPI_SECRET_KEY'),
'Content-Type: application/json',
'Idempotency-Key: order_1042',
],
]);
$payment = json_decode(curl_exec($ch), true, flags: JSON_THROW_ON_ERROR);
import os, requests
response = requests.post(
"https://cryptopayin.com/v1/payments",
headers={
"Authorization": f"Bearer {os.environ['CPI_SECRET_KEY']}",
"Idempotency-Key": "order_1042",
},
json={
"amount": 49.99, "currency": "USD", "asset": "ETH",
"order_ref": "order_1042",
"redirect_url": "https://shop.example/orders/1042/paid",
}, timeout=15,
)
response.raise_for_status()
payment = response.json()Exploitez la réponse
Conservez le id du paiement à côté de votre commande, puis redirigez le client vers checkout_url. Ne calculez jamais vous-même un montant en crypto ou une adresse de dépôt.
{
"id": "P-9F27C1E4KD",
"object": "payment",
"status": "created",
"amount": 49.99,
"amount_decimal": "49.99",
"amount_minor": 4999,
"currency": "USD",
"currency_minor_units": 2,
"amount_usd": 49.99,
"amount_usd_cents": 4999,
"fx_rate_usd": "1.000000000000",
"fx_source": "fixed:USD",
"fx_observed_at": "2026-07-17T13:00:00+00:00",
"fx_discrepancy_bps": 0,
"asset": "ETH",
"network": "mainnet",
"crypto_amount": "0.01388612",
"crypto_received": "0",
"deposit_address": "0x71b8c3d4700000000000000000000000000084e2",
"exchange_rate": "3600.00000000",
"exchange_rate_currency": "USD",
"exchange_rate_source": "median:cb,cg,cl",
"exchange_rate_source_count": 3,
"exchange_rate_observed_at": "2026-07-17T13:00:00+00:00",
"exchange_rate_discrepancy_bps": 12,
"confirmations": 0,
"confirmations_required": 12,
"order_ref": "order_1042",
"checkout_url": "https://cryptopayin.com/i/P-9F27C1E4KD",
"expires_at": "2026-07-17T13:30:00+00:00",
"created_at": "2026-07-17T13:00:00+00:00",
"completed_at": null
}Authentification
Chaque appel API utilise la clé secrète dans un en-tête HTTP Bearer. Les clés secrètes commencent par csk_live_. La valeur cpk_live_ associée est un identifiant public pour votre tableau de bord et ne doit jamais être utilisée comme identifiant Bearer.
Authorization: Bearer csk_live_...Chaque endpointLe secret brut n'est renvoyé qu'à la création de la clé. CryptoPayIn stocke un hachage de mot de passe ainsi qu'un index de recherche SHA-256, jamais le secret en clair.
Créez des clés distinctes par application ou environnement et révoquez-les indépendamment. Un compte peut détenir jusqu'à 50 clés actives.
Ne placez jamais une valeur csk_live_ dans du JavaScript exécuté dans le navigateur, un binaire mobile, un dépôt public ou une page de paiement.
Un endpoint de webhook peut couvrir tout le compte ou être rattaché à une seule clé API, ce qui garde les intégrations isolées.
Un secret manquant, malformé, révoqué ou inconnu renvoie 401 unauthorized. Un compte marchand suspendu ou fermé est rejeté de la même façon. Une clé valide utilisée en dehors de ses autorisations accordées renvoie 403 insufficient_scope.
Idempotence
Envoyez un Idempotency-Key unique à chaque création de paiement. Si votre connexion est interrompue après l'envoi, renvoyez le JSON identique avec la clé identique : CryptoPayIn renvoie le paiement d'origine au lieu d'allouer une nouvelle adresse.
| Cas | Résultat | HTTP |
|---|---|---|
| Première utilisation | Crée et renvoie un nouveau paiement. | 201 |
| Même clé + même JSON | Renvoie le paiement existant avec Idempotent-Replayed: true. | 200 |
| Même clé + JSON différent | Rejette la requête en tant que idempotency_conflict. | 409 |
Les clés sont propres aux identifiants API et peuvent contenir de 1 à 128 lettres, chiffres, points, tirets bas, deux-points ou tirets. Un UUID de commande durable constitue un bon choix. Le même mécanisme protège aussi les retraits, de sorte qu'un retrait renvoyé ne peut jamais déplacer les fonds deux fois.
Référence de l'API
L'API couvre les paiements, les liens de paiement, les boutiques, les produits, les soldes et les retraits. Les réponses utilisent du JSON UTF-8 sur HTTPS ; toute création ou mise à jour requiert Content-Type: application/json. Les mutations sont soumises aux autorisations de la clé appelante. Il n'existe volontairement aucun flux CORS pour navigateur : les appels doivent provenir de votre backend.
/v1/assetsDécouvrir les actifs disponibles/v1/currenciesDécouvrir les devises de présentation fiat/v1/paymentsCréer un paiement/v1/paymentsLister et filtrer les paiements/v1/payments/{id}Récupérer un paiementLa version 1 peut recevoir des champs et endpoints rétrocompatibles. Un changement de contrat non rétrocompatible utilisera un nouveau chemin de base plutôt que de modifier silencieusement /v1.
Lister les actifs
/v1/assetsAuthentification Bearer requiseUtilisez cet endpoint comme source de vérité pour les choix proposés au paiement. Il renvoie les entrées du catalogue activées, les taux actuels vérifiés de façon indépendante, les montants minimums en équivalent USD, ainsi que l'état de préparation du nœud et du flux de prix. Une entrée peut rester listée avec available: false tant qu'un nœud est en cours de synchronisation ou que son prix ne peut pas être vérifié.
curl https://cryptopayin.com/v1/assets \
--header "Authorization: Bearer $CPI_SECRET_KEY"{
"object": "list",
"data": [{
"asset": "USDT",
"network": "TRC20",
"type": "token",
"decimals": 6,
"minimum_amount": 1,
"currency": "USD",
"base_confirmations": 19,
"available": true,
"rate_usd": "1.00000000",
"rate_source": "median:cb,cg,cl",
"rate_source_count": 3,
"rate_discrepancy_bps": 4,
"rate_status": "healthy",
"rate_updated_at": "2026-07-17T13:00:00+00:00"
}]
}Catalogue et identifiants réseau
| Actif | Valeur réseau | Abréviation | Confirmations de base |
|---|---|---|---|
| BTC | mainnet | BTC | 2 |
| ETH | mainnet | ETH | 6 |
| USDT | TRC20 / ERC20 | USDT.TRC20 | 19 / 6 |
| USDC | ERC20 | USDC.ERC20 | 6 |
| DAI | ERC20 | DAI | 6 |
| SHIB | ERC20 | SHIB | 6 |
| PEPE | ERC20 | PEPE | 6 |
| LTC | mainnet | LTC | 6 |
| TRX | mainnet | TRX | 19 |
| DOGE | mainnet | DOGE | 20 |
| XMR | mainnet | XMR | 10 |
| SOL | mainnet | SOL | 32 |
La disponibilité est dynamique. Ne codez pas en dur le tableau ci-dessus comme liste d'autorisation en direct. Pour les symboles multi-réseaux comme USDT, envoyez network explicitement ou utilisez l'abréviation ASSET.NETWORK.
Lister les devises fiat
/v1/currenciesAuthentification Bearer requiseRenvoie les devises de présentation activées, leur précision ISO et l'état actuel de la conversion USD. Ne proposez que les lignes avec available: true. L'USD est intrinsèque ; toute autre devise nécessite une cotation en direct récente et une vérification de référence indépendante. minimum_amount convertit dans cette devise le plancher le plus bas configuré parmi les actifs activés ; l'actif choisi peut exiger un montant plus élevé, pensez donc à toujours lire aussi GET /v1/assets.
curl https://cryptopayin.com/v1/currencies \
--header "Authorization: Bearer $CPI_SECRET_KEY"{
"object": "list",
"accounting_currency": "USD",
"data": [{
"currency": "EUR",
"name": "Euro",
"symbol": "€",
"minor_units": 2,
"available": true,
"minimum_amount": "0.86",
"rate_usd": "1.160000000000",
"rate_source": "coinbase+ecb",
"rate_source_count": 2,
"rate_discrepancy_bps": 18,
"rate_updated_at": "2026-07-17T13:00:00+00:00",
"rate_reference_at": "2026-07-16T00:00:00+00:00"
}]
}Une devise omise sur POST /v1/payments signifie toujours USD par souci de rétrocompatibilité. Les devises sans décimale comme le JPY rejettent les montants fractionnaires. Traitez les minimums et taux du catalogue comme des données en direct, jamais comme des constantes codées en dur.
Créer un paiement
/v1/payments120 requêtes / minute / cléCrée une facture dans la devise de présentation demandée, verrouille des instantanés fiat/USD et crypto/USD fraîchement vérifiés, calcule le montant exact en crypto et associe une adresse de dépôt on-chain dédiée.
Corps de la requête
| Champ | Type | Exigence | Description |
|---|---|---|---|
| amount | nombre ou chaîne décimale | requis | Valeur en currency, à la précision ISO de cette devise. Son équivalent USD verrouillé ne doit pas dépasser $1,000,000.00 ; les minimums propres à l'actif s'appliquent également. |
| currency | string | facultatif | Devise à 3 lettres activée, tirée de GET /v1/currencies. Par défaut : USD. |
| asset | string | requis | Symbole tel que ETH, ou abréviation telle que USDT.TRC20. |
| network | string | conditionnel | Requis lorsqu'un symbole existe sur plusieurs réseaux. Exemple : ERC20. |
| order_ref | string | facultatif | Votre identifiant de commande, 128 caractères maximum. Renvoyé dans les réponses API et les évènements. |
| customer_email | string | facultatif | Adresse e-mail valide, 190 caractères maximum. Stockée avec l'enregistrement de paiement du marchand. |
| redirect_url | string | facultatif | URL HTTPS, 255 caractères maximum, proposée après un paiement réussi. |
Champs de la réponse
| Champ | Type | Description |
|---|---|---|
| id | string | Identifiant de paiement stable commençant par P-. |
| status | string | État actuel du cycle de vie. |
| amount / amount_decimal / amount_minor | number / string / integer | Valeur de présentation demandée sous forme pratique, décimale exacte et en unité mineure ISO. |
| currency / currency_minor_units | string / integer | Devise de présentation verrouillée et sa précision. |
| amount_usd / amount_usd_cents | number / integer | Valeur comptable interne en USD, immuable. |
| fx_rate_usd / fx_source / fx_observed_at | decimal string / string / ISO 8601 | Instantané verrouillé du taux USD par unité de présentation et ses métadonnées d'audit. |
| asset / network | string | Actif on-chain résolu. |
| crypto_amount | decimal string | Montant exact que le client doit envoyer. Ne traitez jamais les décimales crypto comme des flottants binaires. |
| crypto_received | decimal string | Total actuellement observé à l'adresse de dépôt. |
| deposit_address | string | Adresse dédiée allouée pour ce paiement. |
| exchange_rate | decimal string | Taux crypto/USD verrouillé utilisé pour calculer crypto_amount ; ce champ conserve sa signification d'origine de la v1. |
| exchange_rate_source / exchange_rate_observed_at | string / ISO 8601 | Instantané d'audit immuable du taux crypto. |
| confirmations | integer | Nombre actuel de confirmations réseau. |
| confirmations_required | integer | Seuil requis pour ce paiement. Les paliers USD plus élevés peuvent exiger des confirmations supplémentaires. |
| checkout_url | URL | Facture hébergée à présenter au client. |
| expires_at | ISO 8601 | Date limite pour une facture impayée. |
| completed_at | ISO 8601 / null | Heure de règlement final une fois le paiement terminé. |
Les deux conversions en direct sont vérifiées en termes de fraîcheur, de nombre de sources et de divergence avant la création. Si la vérification échoue, la création renvoie une erreur plutôt que d'utiliser un taux obsolète. Le montant en crypto est arrondi vers le haut à une précision utile pour l'actif, de sorte que l'arrondi ne pénalise jamais le marchand.
Lister les paiements
/v1/payments240 requêtes / minute / cléRenvoie les paiements les plus récents en premier pour le compte marchand authentifié. Utilisez la pagination par curseur pour le rapprochement, et des filtres exacts pour retrouver une commande sans parcourir tout l'historique.
Paramètres de requête
| Paramètre | Valeur par défaut | Description |
|---|---|---|
| limit | 20 | Taille de page, de 1 à 100. |
| starting_after | — | ID de paiement renvoyé comme next_cursor de la page précédente. |
| status | — | Statut exact du cycle de vie, tel que pending, completed ou expired. |
| order_ref | — | Référence de commande marchande exacte, 128 caractères maximum. |
curl "https://cryptopayin.com/v1/payments?status=completed&limit=20" \
--header "Authorization: Bearer $CPI_SECRET_KEY"{
"object": "list",
"data": [{
"id": "P-9F27C1E4KD",
"object": "payment",
"status": "completed",
"amount": 49.99,
"currency": "USD",
"asset": "ETH",
"network": "mainnet",
"crypto_amount": "0.01388612",
"crypto_received": "0.01388612",
"order_ref": "order_1042",
"confirmations": 12,
"confirmations_required": 12,
"completed_at": "2026-07-17T13:12:42+00:00"
}],
"has_more": true,
"next_cursor": "P-9F27C1E4KD"
}Lorsque has_more vaut true, transmettez next_cursor sans le modifier en tant que starting_after. Les paramètres de requête inconnus ou répétés de type tableau sont rejetés plutôt qu'ignorés.
Récupérer un paiement
/v1/payments/{id}240 requêtes / minute / cléRenvoie le même objet paiement qu'à la création, avec un statut actualisé, le montant reçu, le hash de transaction et les confirmations. Une clé ne peut récupérer que les paiements appartenant à son compte marchand.
curl https://cryptopayin.com/v1/payments/P-9F27C1E4KD \
--header "Authorization: Bearer $CPI_SECRET_KEY"Les webhooks doivent piloter les mises à jour normales des commandes. Utilisez la récupération pour rapprocher les données après un délai dépassé, vérifier un évènement, afficher une page de statut côté backend ou réparer des livraisons manquées.
Cycle de vie d'un paiement
Considérez toujours le statut renvoyé par l'API comme faisant foi. Ne déduisez jamais qu'un paiement est terminé à partir d'une redirection navigateur ou de l'affirmation du client selon laquelle il a payé.
| Statut | Signification | Action du marchand |
|---|---|---|
| created | Facture et adresse allouées ; aucun financement détecté pour l'instant. | Afficher le paiement hébergé. |
| pending | En attente d'un paiement on-chain exploitable. | Laisser la commande ouverte. |
| underpaid | Des fonds sont arrivés en dessous de la tolérance du marchand. | Demander au payeur d'envoyer le solde affiché. |
| confirming | Valeur suffisante détectée ; en attente des confirmations. | Ne pas encore livrer. |
| completed | Valeur requise et confirmations atteintes. | Livrer une seule fois. |
| overpaid | Plus que prévu a été confirmé. | Livrer et examiner l'excédent. |
| expired | Aucun paiement valable n'a été détecté avant l'expiration. | Créer un nouveau paiement. |
| failed | L'allocation de l'adresse ou le traitement a échoué. | Consigner l'erreur et créer un nouveau paiement. |
Les transferts on-chain sont irréversibles et CryptoPayIn ne dispose d'aucun mécanisme de remboursement — un paiement confirmé est définitif. Tout geste commercial éventuel se règle directement entre vous et votre client, en dehors de la plateforme.
Paiement hébergé
Chaque paiement créé via l'API inclut un checkout_url adapté à tous les écrans. Il affiche le marchand, le montant de présentation demandé, l'équivalent USD verrouillé le cas échéant, le montant exact en crypto, l'adresse de dépôt, le QR code, un avertissement réseau, un compte à rebours et la progression des confirmations en direct.
Le client voit le même crypto_amount que celui renvoyé par l'API pendant la fenêtre de la facture.
Le payeur ne crée pas de compte CryptoPayIn et ne partage aucun identifiant.
La page interroge le paiement en toute sécurité et passe de l'attente à la confirmation puis au paiement effectué.
Une redirect_url HTTPS est proposée après le succès ; elle ne constitue pas une preuve de paiement.
Conservez la livraison côté backend. La navigation dans le navigateur peut être abandonnée, répétée ou falsifiée ; seul un webhook vérifié ou un GET authentifié prouve l'état réel du paiement.
Autorisations & scopes
Chaque clé API porte un ensemble fixe d'autorisations choisies lors de sa création dans Tableau de bord → Développeurs. Chaque endpoint vérifie les scopes de la clé avant toute action ; un appel en dehors du périmètre accordé renvoie 403 insufficient_scope avec un en-tête X-Required-Scope indiquant l'autorisation manquante. Les scopes sont définis une fois pour toutes à la création et ne peuvent pas être élargis ensuite — générez plutôt une nouvelle clé. Les clés créées avant l'existence des scopes conservent exactement leur capacité d'origine : payments:read et payments:write.
| Scope | Accorde | Endpoints |
|---|---|---|
payments:read | Lister et récupérer les paiements | GET /v1/payments, GET /v1/payments/{id} |
payments:write | Créer des paiements hébergés | POST /v1/payments |
links:read | Lister et récupérer les liens de paiement | GET /v1/links, GET /v1/links/{id} |
links:write | Créer, modifier, mettre en pause et supprimer des liens de paiement | POST/PATCH/DELETE /v1/links |
shops:read | Lister et récupérer les boutiques et leurs produits | GET /v1/shops, GET .../products |
shops:write | Créer et modifier des boutiques, produits et variantes | POST/PATCH/DELETE /v1/shops et produits |
balance:read | Lire les soldes crypto et les estimations en USD | GET /v1/balance |
payouts:read | Lister et récupérer les retraits | GET /v1/payouts, GET /v1/payouts/{id} |
payouts:write | Demander des retraits on-chain | POST /v1/payouts |
payouts:write déplace des fonds on-chain et est irréversible. Accordez-le uniquement aux clés en qui vous avez pleinement confiance, conservez ces clés côté serveur, et préférez une clé dédiée par processus automatisé. GET /v1/account indique les scopes de la clé appelante ainsi que les limites de votre compte.
Liens de paiement
Des liens hébergés réutilisables qu'un client peut payer un nombre illimité de fois. Un lien applique la même logique de tarification, d'actif, de livraison et de questions de paiement que le créateur de liens du tableau de bord — l'API se contente de le piloter. Un compte détient jusqu'à 50 liens de paiement.
/v1/linkslinks:read/v1/linkslinks:write/v1/links/{id}links:read/v1/links/{id}links:write/v1/links/{id}links:writeCorps de la requête
| Champ | Type | Exigence | Description |
|---|---|---|---|
| title | string | requis | 3–120 caractères. |
| description | string | facultatif | Jusqu'à 2,000 caractères, affichée lors du paiement. |
| template | string | facultatif | Thème de paiement : signature (par défaut), midnight, atelier, horizon, compact ou ledger. |
| public_label | string | facultatif | Nom de vendeur public affiché aux acheteurs (2–80 caractères). Jamais un identifiant de compte. |
| amount_type | string | facultatif | fixed (par défaut) ou open (le client choisit dans les limites min/max). |
| currency | string | facultatif | Devise de présentation tirée de GET /v1/currencies. Par défaut, la devise de votre compte. |
| amount | number or string | conditionnel | Requis pour fixed. En currency à sa précision ISO. |
| min / max | number or string | conditionnel | Bornes pour les liens open. max peut être 0 ou omis pour ne fixer aucun plafond. |
| accepted_assets | array of strings | facultatif | Codes d'actifs tels que ["BTC","USDT.TRC20"]. Omettre pour inclure tous les actifs disponibles. |
| max_uses | integer | facultatif | Plafond de paiements aboutis. 0 signifie illimité. |
| expires_at | ISO 8601 | facultatif | Au moins 5 minutes dans le futur, au plus 12 mois. UTC. |
| delivery_type | string | facultatif | none, text, url ou keys — biens numériques livrés après paiement. |
| delivery_text / delivery_url | string | conditionnel | Contenu (≤50,000 caractères) ou une URL https pour le type de livraison correspondant. |
| delivery_keys | array of strings | conditionnel | Une clé par élément pour la livraison keys. Jusqu'à 10,000, chacune ≤500 caractères. |
| checkout_fields | array | facultatif | Jusqu'à 5 objets {label, type, required} ; le type est text, email, textarea ou number. |
| success_message / redirect_url | string | facultatif | Message post-paiement (≤500 caractères) et une redirection https. |
| status | string | facultatif | PATCH uniquement : active ou paused. |
curl -X POST https://cryptopayin.com/v1/links \
-H "Authorization: Bearer $CPI_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Pro license",
"amount": 49.99,
"currency": "EUR",
"accepted_assets": ["BTC", "ETH", "USDT.TRC20"],
"delivery_type": "keys",
"delivery_keys": ["ABC-1", "ABC-2", "ABC-3"]
}'{
"id": "PL-N5PKTYB7",
"object": "payment_link",
"url": "https://cryptopaylink.co/pay/PL-N5PKTYB7",
"status": "active",
"title": "Pro license",
"amount_type": "fixed",
"currency": "EUR",
"amount": 49.99,
"amount_decimal": "49.99",
"accepted_assets": [{"asset":"BTC","network":"mainnet"},{"asset":"ETH","network":"mainnet"},{"asset":"USDT","network":"TRC20"}],
"uses": {"started": 0, "completed": 0, "in_flight": 0},
"delivery": {"type": "keys", "keys_available": 3, "keys_total": 3},
"expires_at": null,
"created_at": "2026-07-19T19:00:00+00:00"
}PATCH est une mise à jour partielle : n'envoyez que les champs modifiés, le reste est préservé, y compris les clés de licence non vendues. Pour les liens keys, l'envoi de delivery_keys remplace le pool de clés non vendues ; les clés déjà livrées ne sont jamais touchées. La suppression d'un lien ayant des paiements est refusée implicitement, en conservant son historique intact.
Paiement par agent
Chaque lien de paiement actif est aussi un paiement lisible par une machine : un agent IA ou n'importe quel script peut le découvrir, créer une facture et lire la livraison sans navigateur — et sans aucune clé API, car il s'agit d'endpoints acheteurs publics sur le domaine du lien, pas d'endpoints marchand. Contrat complet et exemple concret : cryptopayin.com/agents.
https://cryptopaylink.co/pay/{link}.jsonpublic · découverteRenvoie l'état, la tarification, les actifs acceptés et le contrat exact des données d'entrée pour l'appel de facturation (champs requis, schéma de livraison, variantes).
https://cryptopaylink.co/pay/{link}/invoicepublic · prend en charge Idempotency-KeyCrée la facture en passant par le même cœur applicatif, le même instantané de tarification et les mêmes limites anti-abus que la page hébergée, et renvoie l'adresse de dépôt, le montant exact en crypto, un URI de portefeuille et l'URL du reçu. L'agent règle ensuite on-chain depuis n'importe quel portefeuille qu'il contrôle.
https://cryptopaylink.co/pay/{link}/receipt?p={payment}public · interrogation toutes les 5–10 sStatut et confirmations en direct ; une fois le paiement terminé, la réponse porte la livraison — votre contenu texte, une URL privée ou une clé de licence réservée à ce paiement — ainsi que votre message de succès et votre URL de redirection.
Les boutiques parlent le même protocole
Les vitrines exposent le même parcours sur leur propre domaine : le catalogue avec le stock en direct, puis un unique appel qui valide le panier, réserve le stock et renvoie la facture.
https://shopycrypto.com/s/{shop}.jsonpublic · cataloguehttps://shopycrypto.com/s/{shop}/orderpublic · panier → facture, prend en charge Idempotency-Keyhttps://shopycrypto.com/s/{shop}/o/{order}/receipt?p={payment}public · interrogation toutes les 5–10 sContrôles vendeur
Le paiement par agent est activé par défaut et coûte la même commission fixe de 1 %. Désactivez-le pour tout le compte dans Tableau de bord → Paramètres → Général → IA & paiement par agent : les endpoints machine des liens et boutiques répondent alors 403 agents_disabled tandis que vos pages de paiement pour humains continuent de fonctionner. Les paiements créés par des agents ne portent aucun indicateur particulier — ce sont des paiements ordinaires dans votre tableau de bord, vos webhooks et vos exports.
Boutiques
Une vitrine hébergée qui regroupe des produits sous une seule page à votre marque. Un compte détient jusqu'à 10 boutiques. Les produits sont gérés via les endpoints produits imbriqués ci-dessous.
/v1/shopsshops:read/v1/shopsshops:write/v1/shops/{id}shops:read/v1/shops/{id}shops:write/v1/shops/{id}shops:writeCorps de la requête
| Champ | Type | Exigence | Description |
|---|---|---|---|
| name | string | requis | 2–80 caractères. |
| tagline | string | facultatif | Jusqu'à 160 caractères. |
| theme | string | facultatif | light (par défaut) ou dark. |
| accent | string | facultatif | Couleur d'accent hexadécimale tirée de la palette de la boutique, renvoyée en tant que accent_palette sur GET /v1/shops. |
| accepted_assets | array of strings | facultatif | Actifs par défaut pour les produits de la boutique, par ex. ["BTC","LTC","XMR"]. Appliqué à tous les produits en cas de modification. |
| status | string | facultatif | PATCH uniquement : active ou paused. |
Une boutique désactivée par CryptoPayIn pour des raisons de conformité aux règles ne peut être ni réactivée ni supprimée via l'API, et renvoie admin_disabled (403). La suppression d'une boutique retire ses produits ; les paiements passés restent intacts.
Produits & variantes
Les produits vivent au sein d'une boutique. Chaque boutique détient jusqu'à 50 produits. Un produit peut être numérique (avec livraison instantanée) ou physique (avec pays de livraison), et peut exposer jusqu'à 30 combinaisons de variantes construites à partir de 1–3 groupes d'options.
/v1/shops/{shop}/productsshops:read/v1/shops/{shop}/productsshops:write/v1/shops/{shop}/products/{id}shops:read/v1/shops/{shop}/products/{id}shops:write/v1/shops/{shop}/products/{id}shops:writeCorps de la requête
| Champ | Type | Exigence | Description |
|---|---|---|---|
| title | string | requis | 3–120 caractères. |
| description / blurb | string | facultatif | Description complète et une ligne de fiche produit de ≤200 caractères. |
| emoji | string | facultatif | Un seul emoji affiché sur la fiche produit. |
| featured | boolean | facultatif | Au plus un produit mis en avant par boutique. |
| product_type | string | facultatif | digital (par défaut) ou physical. |
| shipping_countries | array of strings | conditionnel | Physique uniquement : codes ISO tels que ["FR","BE"], ou ["*"] pour le monde entier. |
| amount_type / currency / amount / min / max | mixed | conditionnel | Tarification de base, règles identiques aux liens de paiement. Les produits physiques doivent être fixed. |
| max_uses | integer | facultatif | Plafond total de ventes (0 = illimité). |
| delivery_type + delivery_text/url/keys | mixed | facultatif | Livraison numérique pour le produit de base, mêmes formats que pour les liens de paiement. |
| variant_options | array | facultatif | 1–3 groupes {name, values[]}, chacun avec 2–10 valeurs. Les combinaisons ne doivent pas dépasser 30. |
| variants | array | conditionnel | Un objet par combinaison (voir ci-dessous). Requis et exhaustif lorsque variant_options est présent. |
| status | string | facultatif | PATCH uniquement : active ou paused. |
Objet variante
| Champ | Type | Description |
|---|---|---|
| options | array of strings | Une valeur par groupe d'options, dans l'ordre des groupes, par ex. ["Pro","Lifetime"]. |
| price | number or string | Prix de la variante dans la devise du produit. |
| stock | integer or null | Unités restantes, ou null pour illimité. |
| delivery_type + delivery_text/url/keys | mixed | Substitution facultative de la livraison numérique par variante (inherit par défaut). Les clés doivent être uniques sur l'ensemble du produit. |
curl -X POST https://cryptopayin.com/v1/shops/SH-JYRK8TFJ/products \
-H "Authorization: Bearer $CPI_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Software license",
"amount": 30,
"currency": "USD",
"variant_options": [
{"name": "Edition", "values": ["Standard", "Pro"]},
{"name": "Term", "values": ["1 year", "Lifetime"]}
],
"variants": [
{"options": ["Standard","1 year"], "price": 30, "delivery_type": "keys", "delivery_keys": ["S1Y-1"]},
{"options": ["Standard","Lifetime"], "price": 79, "stock": 10, "delivery_type": "keys", "delivery_keys": ["SLT-1"]},
{"options": ["Pro","1 year"], "price": 59, "delivery_type": "keys", "delivery_keys": ["P1Y-1"]},
{"options": ["Pro","Lifetime"], "price": 149, "stock": 5, "delivery_type": "keys", "delivery_keys": ["PLT-1"]}
]
}'PATCH préserve les commandes existantes et les clés déjà livrées. Pour ajuster les prix ou le stock, renvoyez le tableau variants correspondant ; les combinaisons omises sont mises en pause si elles ont des commandes, sinon supprimées. Un produit peut détenir au maximum 10,000 clés de licence actives sur l'ensemble de sa base et de ses variantes, et chaque clé doit être unique au sein du produit.
Solde
/v1/balancebalance:readRenvoie vos soldes crypto réglés par actif, avec une estimation USD de meilleur effort et les frais réseau facturés lors d'un retrait. La comptabilité interne est toujours en USD ; les soldes s'accumulent à partir des paiements terminés, nets de la commission marchand.
{
"object": "list",
"accounting_currency": "USD",
"minimum_payout_usd": 25,
"data": [{
"asset": "USDT",
"network": "TRC20",
"amount": "99.099",
"usd_estimate": 99.02,
"payout_network_fee": "2.445463"
}]
}usd_estimate vaut null lorsqu'un taux en direct vérifié est momentanément indisponible ; le solde sous-jacent reste exact. Utilisez ces valeurs pour décider des retraits, pas pour la comptabilité finale.
Retraits
Transférez de la crypto réglée vers un portefeuille externe. Les retraits étant irréversibles, cet endpoint applique toutes les mêmes garanties que le tableau de bord : une destination valide pour l'actif, un taux en direct vérifié, le minimum du compte, un solde suffisant incluant les frais réseau, et une confirmation à deux facteurs lorsque la 2FA est activée sur votre compte.
/v1/payoutspayouts:read/v1/payoutspayouts:write · 30 / min / clé/v1/payouts/{id}payouts:readCorps de la requête
| Champ | Type | Exigence | Description |
|---|---|---|---|
| asset | string | requis | Symbole ou abréviation, par ex. LTC ou USDT.TRC20. |
| network | string | conditionnel | Requis lorsque le symbole existe sur plusieurs réseaux. |
| amount | number or string | requis | Montant à envoyer, hors frais réseau, à la précision de l'actif. Sa valeur en USD doit atteindre le minimum du compte. |
| address | string | requis | Adresse de destination, validée pour la blockchain de l'actif. |
| note | string | facultatif | Votre propre référence, jusqu'à 255 caractères. |
| totp_code | string | conditionnel | Code actuel à 6 chiffres ou code de récupération. Requis lorsque la 2FA est activée sur le compte. |
curl -X POST https://cryptopayin.com/v1/payouts \
-H "Authorization: Bearer $CPI_SECRET_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: withdraw-2026-07-19-01" \
-d '{
"asset": "USDT.TRC20",
"amount": "50",
"address": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
"note": "weekly settlement"
}'{
"id": "W-EM64ZCJB",
"object": "payout",
"status": "requested",
"asset": "USDT",
"network": "TRC20",
"amount": "50",
"fee": "2.445463",
"total_debited": "52.445463",
"address": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
"txid": null,
"confirmations": 0,
"created_at": "2026-07-19T19:10:00+00:00"
}Cycle de vie du statut
| Statut | Signification |
|---|---|
requested | Réservé sur votre solde ; en attente d'examen par un opérateur ou d'approbation automatique. |
approved / processing | Validé et mis en file d'attente pour diffusion par l'exécuteur. |
sent | Diffusé on-chain ; txid est renseigné. |
confirmed | A atteint les confirmations requises. Définitif. |
failed | N'a pas pu être envoyé ; failure_message en explique la raison et le solde est restitué. |
cancelled | Annulé avant diffusion ; le solde réservé est restitué. |
Envoyez un Idempotency-Key afin qu'une nouvelle tentative réseau ne puisse jamais créer un second retrait : la même clé avec le même corps renvoie le retrait d'origine (Idempotent-Replayed: true) ; la même clé avec un corps différent renvoie 409 idempotency_conflict. Le code 2FA est volontairement exclu de l'empreinte d'idempotence, afin qu'un code qui change ne déclenche pas un faux conflit. La réservation débite immédiatement votre solde ; un retrait échoué ou annulé le restitue.
Compte
/v1/accounttoute clé valideRenvoie le profil de votre compte, les scopes de la clé appelante, votre commission de plateforme et toutes les limites en vigueur — utile pour une intégration auto-configurable ou une vérification préalable.
{
"object": "account",
"id": "MC67T3PHQZ",
"fee_bps": 100,
"fee_percent": 1,
"default_currency": "USD",
"two_factor_enabled": false,
"api_key": {"label": "production", "scopes": ["payments:read","payments:write"]},
"limits": {
"shops": {"used": 1, "max": 10},
"payment_links": {"used": 0, "max": 50},
"products_per_shop_max": 50,
"variant_combinations_per_product_max": 30,
"license_keys_per_product_max": 10000,
"active_api_keys_max": 50,
"checkout_fields_per_link_max": 5
},
"minimum_payout_usd": 25
}Webhooks
Ajoutez jusqu'à 10 endpoints HTTPS publics dans Tableau de bord -> Développeurs. Chaque endpoint reçoit son propre secret de signature whsec_..., affiché une seule fois. Il peut écouter tout le compte ou être rattaché à une clé API active spécifique ; les endpoints rattachés à une clé ne reçoivent que les paiements créés avec cette clé.
Vérifiez avant de traiter
CryptoPayIn signe le corps brut exact de la requête à l'aide du secret de l'endpoint. La version 1 signe timestamp + "." + raw_body. Rejetez les horodatages obsolètes avant d'accepter l'évènement.
import crypto from "node:crypto";
const timestamp = req.headers["x-cpi-timestamp"];
const signature = req.headers["x-cpi-signature"];
const rawBody = req.rawBody; // Buffer captured before JSON parsing
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
throw new Error("stale webhook");
}
const expected = "sha256=" + crypto
.createHmac("sha256", process.env.CPI_WEBHOOK_SECRET)
.update(Buffer.concat([Buffer.from(`${timestamp}.`), rawBody]))
.digest("hex");
const valid = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
if (!valid) throw new Error("invalid webhook signature");
$rawBody = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_CPI_TIMESTAMP'] ?? '';
$received = $_SERVER['HTTP_X_CPI_SIGNATURE'] ?? '';
if (!ctype_digit($timestamp) || abs(time() - (int)$timestamp) > 300) {
http_response_code(400); exit('stale webhook');
}
$expected = 'sha256=' . hash_hmac(
'sha256', $timestamp . '.' . $rawBody, getenv('CPI_WEBHOOK_SECRET')
);
if (!hash_equals($expected, $received)) {
http_response_code(401); exit('invalid signature');
}
$event = json_decode($rawBody, true, flags: JSON_THROW_ON_ERROR);
import hashlib, hmac, os, time
raw_body = request.get_data() # bytes, before JSON decoding
timestamp = request.headers.get("X-CPI-Timestamp", "")
received = request.headers.get("X-CPI-Signature", "")
if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > 300:
raise ValueError("stale webhook")
signed = timestamp.encode() + b"." + raw_body
expected = "sha256=" + hmac.new(
os.environ["CPI_WEBHOOK_SECRET"].encode(), signed, hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected, received):
raise ValueError("invalid signature")En-têtes de livraison
| En-tête | Exemple | Objet |
|---|---|---|
| Content-Type | application/json | Corps JSON UTF-8. |
| X-CPI-Timestamp | 1784293200 | Secondes Unix incluses dans le message signé. |
| X-CPI-Signature | sha256=... | HMAC-SHA256 en hexadécimal. |
| X-CPI-Signature-Version | v1 | Version du schéma de signature. |
| X-CPI-Event-Id | evt_a12b... | ID logique stable de l'évènement ; identique à chaque nouvelle tentative. |
| X-CPI-Delivery-Id | 1842 | ID stable de l'enregistrement de livraison de l'endpoint. |
Charge utile du paiement
{
"event_id": "evt_a12b34c56d78e90f12345678",
"event": "payment.completed",
"id": "P-9F27C1E4KD",
"status": "completed",
"order_ref": "order_1042",
"amount": 49.99,
"amount_decimal": "49.99",
"amount_minor": 4999,
"currency_minor_units": 2,
"currency": "USD",
"amount_usd": 49.99,
"amount_usd_cents": 4999,
"fx_rate_usd": "1.000000000000",
"fx_source": "fixed:USD",
"fx_observed_at": "2026-07-17T13:00:00Z",
"fx_discrepancy_bps": 0,
"exchange_rate": "3600.00000000",
"exchange_rate_currency": "USD",
"exchange_rate_source": "median:cb,cg,cl",
"exchange_rate_source_count": 3,
"exchange_rate_observed_at": "2026-07-17T13:00:00Z",
"exchange_rate_discrepancy_bps": 12,
"asset": "ETH",
"network": "mainnet",
"crypto_amount": "0.01388612",
"crypto_received": "0.01388612",
"txid": "0x9d81...75af",
"confirmations": 12,
"confirmations_required": 12,
"deposit_address": "0x71b8c3d4700000000000000000000000000084e2",
"sent_at": "2026-07-17T13:12:42Z"
}Nouvelles tentatives et sécurité de l'endpoint
Une livraison réussit avec un code HTTP 200-299. Effectuez les traitements coûteux de façon asynchrone et répondez rapidement.
Le corps exact et l'ID de l'évènement sont conservés ; en cas d'échec, une nouvelle tentative a lieu après environ 1 minute, 5 minutes, 30 minutes, 2 heures puis 6 heures.
Les réponses 3xx ne sont pas suivies. Enregistrez directement l'URL HTTPS finale.
Les IP privées, de boucle locale, link-local et réservées sont bloquées ; chaque réponse DNS est validée et la connexion est épinglée.
Les livraisons se font au moins une fois. Rendez votre gestionnaire idempotent en enregistrant event_id avec une contrainte d'unicité avant la livraison. Récupérez l'objet via l'API lors du rapprochement d'un évènement inattendu.
Référence des évènements
payment.completedValeur attendue confirmée.payment.overpaidPlus que prévu confirmé.payment.underpaidFinancement inférieur à la tolérance détecté.payment.expiredFenêtre de facture impayée fermée.payment.failedÉchec de la configuration ou du traitement du paiement.payout.sentRetrait diffusé on-chain.payout.confirmedRetrait ayant atteint les confirmations.payout.failedLe retrait n'a pas pu aboutir.webhook.testTest de connectivité manuel.Structure de l'évènement de retrait
{
"event_id": "evt_b98c76d54e32a10f87654321",
"event": "payout.sent",
"id": "W-8J2K7M4RQP",
"status": "sent",
"asset": "ETH",
"network": "mainnet",
"amount": "0.25",
"fee": "0.00081768",
"address": "0x84f2...9bc1",
"txid": "0xa1c4...07ee",
"confirmations": 0,
"note": "weekly treasury",
"sent_at": "2026-07-17T14:08:31Z"
}Erreurs et limites de débit
Les erreurs utilisent toujours une seule enveloppe JSON. Basez votre logique sur error.type ; le message destiné aux humains peut être amélioré sans changement de version. Citez error.request_id ou l'en-tête de réponse X-Request-Id correspondant lorsque vous contactez le support.
{
"error": {
"type": "ambiguous_asset",
"message": "specify network (e.g. USDT.ERC20 or USDT.TRC20)",
"request_id": "b942e21f8dca4b06b8672eb9"
}
}| HTTP | Types courants | Signification |
|---|---|---|
| 400 | invalid_request, unknown_parameter | JSON, requête ou en-tête d'idempotence malformé. |
| 401 | unauthorized | Identifiant ou compte manquant, invalide ou inactif. |
| 403 | insufficient_scope, admin_disabled | Clé valide mais dépourvue de l'autorisation requise (voir X-Required-Scope), ou ressource verrouillée par un administrateur. |
| 404 | not_found | Endpoint inconnu, ou ressource en dehors de ce compte marchand. |
| 405 | method_not_allowed | Utilisez la méthode indiquée dans l'en-tête Allow. |
| 409 | idempotency_conflict | Clé réutilisée avec un JSON différent. |
| 413 | request_too_large | Le corps JSON dépasse 64 Kio. |
| 415 | unsupported_media_type | Le corps du POST n'est pas déclaré comme application/json. |
| 422 | invalid, limit, invalid_amount, below_minimum, bad_address, 2fa_required, unsupported_asset | Requête bien formée ayant échoué à une validation déterministe ou atteint un plafond de ressource. |
| 429 | rate_limited | Patientez jusqu'à Retry-After. |
| 500 | server_error | Échec inattendu ; nouvelle tentative possible en toute sécurité avec la même clé d'idempotence. |
| 503 | maintenance, temporarily_unavailable, asset_unavailable, no_rate, price_stale, fiat_rate_unavailable, fiat_rate_stale, derive_failed | Échec transitoire de la plateforme, d'un nœud, d'un prix ou de l'allocation d'adresse. Aucune conversion obsolète n'est substituée. |
Limites actuelles
| Portée | Limite | Fenêtre |
|---|---|---|
| Plafond de sécurité Nginx par IP | 10 requêtes/seconde, rafale de 30 | Continu |
| Plafond IP non authentifiée | 300 requêtes | 60 secondes |
| POST /v1/payments, links, shops, products | 120 requêtes par clé API | 60 secondes |
| POST /v1/payouts | 30 requêtes par clé API | 60 secondes |
| Endpoints GET | 240 requêtes par clé API | 60 secondes |
Limites de compte
| Ressource | Plafond |
|---|---|
| Boutiques par compte | 10 |
| Liens de paiement par compte | 50 |
| Produits par boutique | 50 |
| Combinaisons de variantes par produit | 30 |
| Clés de licence par produit / lien | 10,000 |
| Questions de paiement par lien | 5 |
| Clés API actives par compte | 50 |
Consultez votre utilisation en direct par rapport à ces plafonds depuis GET /v1/account.
Les réponses réussies limitées au niveau applicatif exposent X-RateLimit-Limit, X-RateLimit-Remaining et X-RateLimit-Reset. Effectuez de nouvelles tentatives pour 429, 500 et 503 avec un backoff exponentiel et du jitter. Pour un POST, réutilisez toujours le Idempotency-Key d'origine et un JSON identique.
Sécurité de l'intégration
Chargez-les à l'exécution ; ne journalisez jamais la valeur complète et ne la validez jamais dans le contrôle de code source.
Un client navigateur ou mobile ne peut pas conserver en toute sécurité un secret marchand.
Vérifiez la fraîcheur de l'horodatage et utilisez une comparaison de signature en temps constant avant l'analyse du JSON.
Enregistrez les évènements/commandes traités de façon transactionnelle afin qu'une nouvelle tentative ne livre jamais deux fois.
Récupérez le paiement lorsqu'un évènement est inattendu ou que votre état local diverge.
Créez une clé de remplacement, déployez-la, vérifiez le trafic, puis supprimez l'ancienne clé.
L'accès au compte est contrôlé par une clé marchand à 16 chiffres non récupérable, éventuellement protégée par TOTP. Conservez à la fois l'accès marchand et les secrets API avec le même soin que des identifiants de portefeuille.
Check-list de mise en production
Ne réutilisez pas la copie personnelle d'un développeur sur plusieurs services.
Utilisez GET /v1/assets et GET /v1/currencies ; n'affichez que les entrées présentes et available: true.
Enregistrez le secret de signature une seule fois ; vérifiez l'horodatage et la signature, puis dédupliquez l'ID d'évènement stable.
Testez une nouvelle tentative en double et vérifiez qu'un seul ID de paiement existe.
L'état de votre commande doit rester sûr face à des taux fiat obsolètes, des actifs indisponibles, des confirmations retardées et tous les chemins non nominaux.
Comparez vos commandes avec les états de paiement de l'API, les journaux de webhooks et le grand livre marchand.
Prêt à intégrer ?
Créez un compte en quelques secondes, générez une clé et gardez cette référence à portée de main pendant que vous codez.