DOCUMENTATION DÉVELOPPEUR
Documentation API
Intègre les factures, la page de paiement et les notifications de paiement.
Résultats de recherche
Aucun résultat. Essaie un nom d'endpoint, de champ ou de guide.
Démarrage rapide
Crée ta première facture.
- Préparer un magasin
Active ses moyens de paiement, configure les fournisseurs et sauvegarde les portefeuilles du projet.
- Créer un identifiant API
Dans Réglages → Accès API de ta console, choisis lecture/écriture et attribue le projet.
- Envoyer la requête
Utilise ton hôte API et copie tes identifiants de projet et de magasin. Envoie les montants décimaux sous forme de chaînes.
- Ouvrir la page de paiement
Redirige vers
links.checkoutrenvoyé dans la réponse. Vérifie le règlement avant d'exécuter la commande.
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new invoice.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: order-1042-attempt-1' \
--header 'Content-Type: application/json' \
--data-raw '{
"amount": "49.90",
"currency": "USD",
"order_id": "order-1042",
"exchange_rate_spread_percent": "0.5"
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new invoice.
const body = `{
"amount": "49.90",
"currency": "USD",
"order_id": "order-1042",
"exchange_rate_spread_percent": "0.5"
}`;
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "order-1042-attempt-1",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new invoice.
$body = <<<'JSON'
{
"amount": "49.90",
"currency": "USD",
"order_id": "order-1042",
"exchange_rate_spread_percent": "0.5"
}
JSON;
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: order-1042-attempt-1", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new invoice.
headers = {
"Idempotency-Key": "order-1042-attempt-1",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"amount": "49.90",
"currency": "USD",
"order_id": "order-1042",
"exchange_rate_spread_percent": "0.5"
}""".encode("utf-8")
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Les exemples utilisent des espaces réservés et n'envoient pas de requêtes depuis cette page. Voir tous les champs de facture et le format de réponse →
Identifiants de projet et magasin
Où trouver YOUR_PROJECT_ID et YOUR_STORE_ID.
Utilise les UUID de ta console, pas les noms des projets ou magasins ni leurs identifiants lisibles.
| Espace réservé | Où le trouver | Utilisé pour |
|---|---|---|
| YOUR_PROJECT_ID | Projet → Réglages → Identifiants API → Identifiant API du projet → Copier. Également affiché dans l'onglet Général du magasin. | Requêtes au niveau du projet et du magasin. |
| YOUR_STORE_ID | Projet → Magasins → sélectionne un magasin → Général → Identifiants API → Identifiant API du magasin → Copier. | Création de factures et requêtes de moyens de paiement du magasin. |
- Créer une facture nécessite les deux identifiants, même pour le magasin par défaut. Le magasin doit appartenir à ce projet et l'identifiant API doit y avoir accès.
- La création, la liste, le détail et le paiement des factures renvoient invoice_id : le même UUID envoyé dans les IPN/webhooks. Utilise-le dans les chemins de factures, pas l'id interne ni order_id. Depuis la version commerçant 4.0.0, l'ancien champ de réponse public_id est supprimé ; mets à jour les intégrations avant la mise à niveau.
- L'API REST ne fournit pas de routes pour lister les projets/magasins. Copie les identifiants dans la console ou utilise les outils MCP à périmètre limité list_projects et list_stores dans la version commerçant 5.0.0+.
- Magasin → Général → Domaines du magasin sélectionne les noms d'hôte actifs commerçant, paiement et API. Les liens de paiement renvoyés et les nouveaux liens de callback privilégient ce magasin, puis le magasin par défaut, puis les réglages système. Les noms retirés ou non activés ne sont jamais sélectionnés. Configure ton SDK avec le nom d'hôte API préféré ; changer une préférence ne redirige pas les autres alias actifs.
Authentification et périmètre
Garde les identifiants sur ton serveur et accorde uniquement les accès nécessaires.
| Hôte par défaut | Fonction |
|---|---|
| merchant.example.com | Console commerçant et Réglages |
| pay.example.com | Page de paiement client |
| api.example.com | Requêtes API commerçant |
Remplace example.com par ton domaine. Les installations existantes conservent leurs noms configurés ; gère les alias dans Réglages → Système.
Authorization: Bearer YOUR_MERCHANT_API_TOKEN| Réglage | Comment ça marche |
|---|---|
| Niveau d'accès | Les identifiants en lecture seule peuvent lister et récupérer les données. Les identifiants en lecture/écriture peuvent aussi créer des factures et mettre à jour les politiques d'actifs documentées. |
| Projets | Attribue les projets auxquels l'identifiant peut accéder. Les identifiants de magasin et de facture doivent appartenir à un projet attribué. |
| Restrictions IP | Tu peux autoriser des adresses de sortie publiques IPv4 ou IPv6 exactes dans Réglages → Accès API. |
| Stockage des identifiants | Garde les jetons dans la configuration de ton backend. N'inclus jamais d'identifiant bearer dans un navigateur ni dans un lien de paiement. |
Les routes publiques de paiement utilisent l'identifiant public de la facture et n'exposent que des données adaptées au paiement public. Les sessions de console et les contrôles administratifs sont distincts des identifiants API commerçant.
Actifs et portefeuilles
Choisis les moyens de paiement indépendamment pour chaque magasin.
- Consulte les actifs de paiement du projet et leur disponibilité.
- Active la blockchain native et configure son portefeuille et ses fournisseurs.
- Parcours les tokens candidats et vérifie le contrat ou le mint avant d'activer un token.
- Sélectionne, dans l'ordre voulu, les moyens de paiement. Les nouvelles factures utilisent ses sélections prêtes à recevoir des paiements.
Les tokens partagent le portefeuille de leur blockchain native. Les soldes des portefeuilles renvoient les montants atomiques exacts et des valeurs fiat indicatives. Utilise les champs de disponibilité renvoyés pour déterminer quels moyens peuvent recevoir des paiements.
Les tokens ERC-20 vérifiés utilisent les réseaux EVM pris en charge ; les tokens SPL vérifiés utilisent Solana. Les moyens de paiement natifs sont disponibles sur les 30 réseaux intégrés. Monero utilise une connexion à un portefeuille externe en consultation seule, liée au projet.
API de réception et couverture native/tokens
| Canal | Prise en charge | Preuves | Exigences |
|---|---|---|---|
| Canaux de paiement natifs | pris en charge | Détection par transactions | BTC, SOL, ETH (Ethereum/Base/Arbitrum/OP), BNB, HYPE, AVAX et POL ; les sorties Bitcoin, transactions/reçus EVM canoniques et transferts Solana analysés fournissent les preuves des factures. |
| Canaux de tokens ERC-20 | pris en charge | Détection par transactions | Ethereum, Base, BNB Chain, HyperEVM, Avalanche, Polygon, Arbitrum et Optimism nécessitent une vérification on-chain ; les journaux Transfer indexés permettent d'attribuer les paiements. |
| Canaux de tokens SPL | pris en charge | Détection par transactions | Les candidats Solana nécessitent une vérification du réseau principal et du mint ; les variations exactes des soldes de tokens dans les transactions analysées permettent d'attribuer les paiements. |
| Autres canaux natifs UTXO | pris en charge | Détection par transactions | BCH/LTC/DOGE utilisent Esplora ; BCH/DOGE acceptent aussi Bitcore, LTC/DOGE/DASH acceptent BlockCypher, Dash accepte Insight et ZEC transparent accepte zcash-explorer. Tous acceptent aussi des blocs node-rpc complets conservés et compatibles Core. Le mode brut nécessite 1–48 confirmations, pas une détection mempool. Zcash protégé n'est pas pris en charge. |
| Canaux natifs de comptes indexés | pris en charge | Détection par transactions | TRON utilise tron-indexer ou node-rpc avec blocs solidifiés ; XRP utilise xrpl-jsonrpc ; Stellar utilise stellar-horizon ou des registres Stellar node-rpc conservés ; Cosmos Hub utilise cometbft-jsonrpc ; Algorand utilise algorand-indexer ou algod node-rpc ; Hedera nécessite hedera-mirror, pas un relais EVM. |
| Canaux de paiement natifs à registre | pris en charge | Détection par transactions | Aptos utilise aptos-rest ; Sui utilise sui-graphql ; NEAR utilise near-jsonrpc ; Kaspa utilise kaspa-rest. Polkadot Asset Hub accepte substrate-rest ou node-rpc finalisé tenant compte des métadonnées ; Tezos accepte tezos-tzkt ou les opérations complètes Octez node-rpc. Réceptions natives uniquement ; la conservation des archives est nécessaire pour les anciennes factures. |
| Canaux natifs Cardano et TON | pris en charge | Détection par transactions | Cardano nécessite cardano-koios et TON nécessite toncenter-v3. Les tags XRP, identifiants de mémo Stellar et commentaires de facture TON sont renvoyés sous destination_tag et doivent être envoyés exactement. |
| Intégrité du règlement | pris en charge | Vérification indépendante | Par défaut, le règlement final exige l'accord de deux fournisseurs indépendants sur la transaction/l'événement exact, le montant, le bloc ou slot canonique et la finalité. Les fenêtres EVM brutes et partagées vérifient aussi la couverture complète. Un administrateur peut explicitement choisir un seul fournisseur de confiance pour une blockchain ; cela supprime la vérification croisée indépendante, pas les contrôles d'identité, de complétude ou de finalité. |
| Canal natif Monero | pris en charge | RPC de portefeuille en consultation seule lié au projet | Un wallet-RPC externe dédié en observation seule, derrière une passerelle HTTPS limitant les méthodes autorisées, crée les sous-adresses du compte 0. Le seuil configuré des démons du réseau principal (2 sources indépendantes par défaut, 1 en option) fournit les preuves de règlement. L'option native --restricted-rpc est incompatible avec create_address ; la sauvegarde du portefeuille et l'absence de clé de dépense sont explicitement attestées par l'opérateur, sans envoyer de clés à Wholly Crypto. |
Les soldes des plateformes et les choix de sweep vers un portefeuille ou une plateforme par actif sont disponibles dans la console, pas via l'API publique v1. Voir la configuration des plateformes d'échange.
Cycle de vie des factures
Preuves de paiement, règlement et exécution des commandes.
| Statut | Signification |
|---|---|
| new | En attente d'un paiement |
| processing | Paiement observé ; montant accepté ou finalité en attente |
| settled | Accepté selon la politique de règlement de la facture ou manuellement |
| expired | Échéance dépassée ; la surveillance des paiements tardifs peut continuer |
| invalid | Le paiement ne peut pas être accepté automatiquement |
| cancelled | Annulé ; seul un rapprochement explicite peut le rouvrir |
amount_status enregistre none, partial, paid ou overpaid. timing_status distingue les paiements à temps des paiements tardifs. Les règles du magasin fixent les confirmations requises et la tolérance de sous-paiement acceptée.
Utilise la valeur de la facture invoice_id avec la route de détail de facture. Une redirection depuis la page de paiement ne prouve pas à elle seule le règlement. Examine les exceptions via le rapprochement.
Nouvelles tentatives sûres
La création de factures nécessite Idempotency-Key. Après un dépassement de délai, réessaie avec le même identifiant, la même clé et exactement le même corps de requête. Utilise une nouvelle clé uniquement pour une nouvelle facture.
Détection des paiements EVM
La détection partagée des blocs natifs et ERC-20 regroupe les factures récentes séparément du rattrapage des anciennes. Chaque facture conserve son curseur d'historique persistant. Les requêtes de tokens utilisent au maximum 100 blocs par requête et se réduisent pour les fournisseurs aux limites plus strictes. Deux fournisseurs indépendants vérifient chaque fenêtre par défaut. Réglages → Connexions blockchain → Détails permet de choisir une seule source de confiance pour une blockchain, sans vérification croisée indépendante ; les contrôles de transaction canonique, montant et confirmations restent. Les détails de connexion distinguent les retards de détection, restrictions d'historique et délais liés aux quotas de l'état de base du nœud. La capacité RPC publique n'est pas garantie.
IPN et webhooks
Reçois et vérifie les événements de paiement.
L'IPN reçoit chaque événement de facture généré à l'ipn_url effectif de la facture. Les webhooks ne reçoivent que les événements sélectionnés pour chaque endpoint activé du magasin. Les deux envoient le même instantané JSON par POST ; ils sont indépendants, donc les activer tous deux peut notifier ton application deux fois.
Définis ipn_url à la création d'une facture, ou hérite de la valeur du magasin. L'IPN utilise le secret de Magasin → IPN ; chaque Magasin → Webhooks endpoint a son propre secret. Aucun des deux n'est ta clé API.
Quand dois-je exécuter une commande ?
Pour un traitement par événements, utilise event_type = invoice.settled avec status = settled pour déclencher la vérification d'une commande. Vérifie la facture actuelle et n'exécute chaque commande qu'une fois.
status est l'état de la facture à la création de l'événement. event_type indique ce qui s'est passé. payment.received peut porter processing ou settled ; cela ne signifie pas un second paiement et ne constitue pas un signal indépendant d'exécution.
Quels événements et statuts sont envoyés ?
| Événement dans les réglages/l'historique | Statut dans le corps | Signification |
|---|---|---|
| invoice.created | new | Facture créée et en attente de paiement. Également utilisé lorsqu'une réouverture contrôlée ramène une facture à new. |
| payment.received | Resulting invoice status | Un paiement a été enregistré ou le montant reçu a augmenté. Généralement processing ou settled ; cet événement seul ne prouve pas le règlement. |
| invoice.processing | processing | Paiement détecté, mais le montant accepté ou la finalité requise n'est pas encore atteint. Les paiements partiels sont inclus. |
| invoice.settled | settled | Politique de règlement satisfaite, ou acceptation manuelle. Vérifie resolution et ta commande avant l'exécution. |
| invoice.expired | expired | Échéance de paiement dépassée. Un paiement tardif peut encore changer le statut tant que la surveillance continue. |
| invoice.invalid | invalid | Ne peut pas être accepté automatiquement, les preuves de paiement ont été perdues ou un commerçant l'a refusé. Examine la facture. |
| invoice.cancelled | cancelled | Facture annulée. N'exécute pas la commande ; une annulation ne rembourse pas un paiement on-chain. |
Pourquoi Ethereum et Solana peuvent envoyer des séquences d'événements différentes
Les confirmations arrivent plus tard (exemple Ethereum)
| Séquence | event_type | status |
|---|---|---|
| 1 | invoice.created | new |
| 2 | payment.received | processing |
| 2 | invoice.processing | processing |
| 3 | invoice.settled | settled |
Déjà définitif à la détection (exemple Solana)
| Séquence | event_type | status |
|---|---|---|
| 1 | invoice.created | new |
| 2 | payment.received | settled |
| 2 | invoice.settled | settled |
Ces exemples montrent la création des événements, pas un ordre de livraison garanti. Les deux parcours peuvent se produire sur d'autres blockchains selon le moment de détection et la politique de règlement. N'exige pas d'événement processing avant settled.
Exécuter une seule fois : exemple de réception et protection contre les doublons
| Approche | Comment le traiter |
|---|---|
| Récepteur basé sur les événements | Garde les événements distincts selon l'event_id signé, puis sélectionne invoice.settled avec status = settled. Ne rejette pas cet événement parce que payment.received avec la même sequence est arrivé en premier. |
| Boîte de réception SDK des états de commande | Les exemples de réception PHP, Python et Node fournis regroupent project + invoice_id + sequence. Traite l'état enregistré quel que soit event_type, récupère la facture actuelle et exécute une seule fois si settled. N'ajoute pas de filtre limité à invoice.settled après ce regroupement. |
Une nouvelle tentative conserve event_id et le corps d'origine. Des événements différents peuvent partager sequence mais avoir des valeurs event_id distinctes. Déduplique les livraisons par event_id signé pour le traitement par événements ; protège séparément l'exécution par installation/projet configuré + invoice_id et ta commande. Un nouveau règlement ultérieur ne doit pas créditer la commande deux fois.
HTTP receiver:
Verify raw-body signature, timestamp and configured project/store scope.
Save to a durable inbox; deduplicate the signed event_id.
Return HTTP 2xx only after persistence succeeds.
Event-based background worker:
Other events go to status/reconciliation handling, not fulfilment.
Continue here only for event_type = invoice.settled and status = settled.
Fetch the current invoice from your configured API origin.
Check settled status, project/store, order, amount, currency and review policy.
In one database transaction:
Lock the order and check the scoped invoice has not been fulfilled.
Credit/complete once and save the fulfilment record.
Queue any external fulfilment with the same business idempotency key.
SDK order-state worker:
Use the same current-invoice checks and fulfil-once transaction.
Do not filter event_type after collapsing events by invoice revision.Pseudocode, pas un récepteur prêt à l'emploi.
Tous les états de facture et exceptions de paiement
| Champ | Valeurs | Signification |
|---|---|---|
| status | new, processing, settled, expired, invalid, cancelled | État de la facture à la création de l'événement ; pas forcément son état actuel à la livraison. |
| amount_status | none, partial, paid, overpaid | Montant reçu, tolérance acceptée comprise. paid ne signifie pas la finalité des confirmations. |
| timing_status | on_time, late | Indique si le paiement a respecté l'échéance de la facture. |
| resolution | automatic, manually_settled, manually_invalidated | Indique si les règles normales ou une acceptation/un refus manuel ont déterminé le résultat. |
| requires_review | false, true | Indication d'exception, pas un autre statut de facture ni une autorisation automatique d'exécution ou de remboursement. |
| Situation | Traitement |
|---|---|
| Sous-paiement / tolérance | Avec les règles automatiques, partial ne règle pas la facture. paid peut inclure un montant manquant accepté, mais la finalité reste requise. Utilise le statut de la facture, pas seulement une comparaison de montants. |
| Surpaiement | overpaid peut coexister avec settled et requires_review = true. Applique ta politique de surpaiement ; ne crédite jamais la commande deux fois et ne rembourse pas automatiquement une adresse non vérifiée. |
| Paiement tardif | expired peut changer plus tard tant que la surveillance continue. timing_status = late signale un examen ; ne rouvre pas et n'expédie pas automatiquement une commande annulée. |
| Acceptation manuelle | invoice.settled peut avoir resolution = manually_settled sans fonds on-chain admissibles. Décide si ton intégration accepte cette dérogation ; les champs récapitulatifs de paiement peuvent être null. |
| Réorganisation / invalidation | Une révision plus récente peut invalider les preuves de paiement antérieures. Récupère l'état actuel et traite l'annulation via le rapprochement. Ne l'ignore pas simplement parce que la commande a déjà été réglée. |
| Zéro confirmation / montant nul | Le règlement sans confirmation peut se produire à la détection et comporte un risque de réorganisation. Une facture de montant nul explicitement autorisée est réglée sans paiement. Aucun des deux cas n'exige d'abord un événement payment.received. |
Utilise status = settled pour l'exécution, pas amount_status = paid ni une redirection de paiement. Avec zéro confirmation requise, le règlement peut se produire à la détection ; cela comporte un risque de réorganisation.
Un sous-paiement correspond à amount_status = partial ; un surpaiement à overpaid. paid signifie que le minimum accepté, y compris la tolérance de sous-paiement de la facture, est arrivé. Ce sont des états de montant, pas des statuts de facture. late est un timing_status, pas un événement distinct.
Un parcours typique est new → processing → settled, mais les états intermédiaires peuvent être sautés. Une facture de montant nul explicitement autorisée est réglée sans paiement et garde amount_status = none. L'acceptation manuelle est marquée manually_settled.
Les callbacks sont des instantanés immuables, pas des réponses de statut en direct. Ils peuvent arriver en retard, dans le désordre ou plusieurs fois. Les événements de paiement et de statut peuvent partager une séquence de facture et les mêmes champs d'état, mais ont des valeurs signées event_id et event_type distinctes. Le nombre de confirmations ne garantit pas un callback à chaque bloc.
Ce que tu reçois
{
"invoice_id": "11111111-2222-4333-8444-555555555555",
"status": "settled",
"amount_status": "paid",
"timing_status": "on_time",
"resolution": "automatic",
"sequence": 3,
"amount": "49.9",
"currency": "EUR",
"order_id": "order-1042",
"payload_version": 2,
"event_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"event_type": "invoice.settled",
"occurred_at": "2026-09-14T12:05:00Z",
"project_id": "11111111-1111-4111-8111-111111111111",
"store_id": "22222222-2222-4222-8222-222222222222",
"description": "Annual plan",
"email": "ada@example.test",
"customer": {
"firstname": "Ada",
"lastname": "Lovelace",
"countryiso2": "GB"
},
"metadata": {
"firstname": "Ada",
"lastname": "Lovelace",
"countryiso2": "GB",
"cart_id": "cart-681"
},
"created_at": "2026-09-14T12:00:00Z",
"updated_at": "2026-09-14T12:05:00Z",
"expires_at": "2026-09-14T12:15:00Z",
"monitoring_expires_at": "2026-09-21T12:15:00Z",
"settled_at": "2026-09-14T12:05:00Z",
"paid_chain": "ethereum",
"paid_asset": "USDC",
"paid_asset_amount": "58.17342",
"paid_asset_amount_received": "58.17342",
"paid_payment_method_id": "33333333-3333-4333-8333-333333333333",
"settlement_exchange_rate": {
"rate": "1.17",
"units": "asset_per_invoice_currency",
"currency": "EUR",
"symbol": "USDC",
"observed_at": "2026-09-14T12:05:00Z",
"as_of": "2026-09-14T12:04:30Z",
"pricing_provider": "kraken",
"asset_provider": "kraken",
"pricing_fetched_at": "2026-09-14T12:04:30Z",
"asset_fetched_at": "2026-09-14T12:04:30Z",
"stale": false,
"is_fixed": false,
"reference_currency": "USD",
"uses_reference_proxy": false
},
"cancelled_at": null,
"exchange_rate_spread_percent": "0.5",
"underpayment_tolerance_percent": "1",
"reason_code": "payment_confirmed",
"requires_review": false,
"links": {
"checkout": "https://pay.example.com/invoice/11111111-2222-4333-8444-555555555555",
"invoice": "https://api.example.com/v1/projects/11111111-1111-4111-8111-111111111111/invoices/11111111-2222-4333-8444-555555555555",
"payments": "https://api.example.com/v1/projects/11111111-1111-4111-8111-111111111111/invoices/11111111-2222-4333-8444-555555555555/payments"
},
"payment_info": {
"active_payment_method_id": "33333333-3333-4333-8333-333333333333",
"method_count": 1,
"methods_truncated": false,
"methods": [
{
"payment_method_id": "33333333-3333-4333-8333-333333333333",
"payment_rail": "onchain",
"chain_slug": "ethereum",
"network": "mainnet",
"caip_network_id": "eip155:1",
"asset_id": "44444444-4444-4444-8444-444444444444",
"asset_key": "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"caip_asset_id": "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"asset_name": "USD Coin",
"symbol": "USDC",
"asset_kind": "token",
"asset_decimals": 6,
"contract_address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"token_standard": "erc20",
"destination_address": "0x1111111111111111111111111111111111111111",
"destination_tag": null,
"status": "paid",
"amounts": {
"expected_amount": "58.17342",
"expected_amount_atomic": "58173420",
"received_amount": "58.17342",
"received_amount_atomic": "58173420",
"confirmed_amount": "58.17342",
"confirmed_amount_atomic": "58173420",
"unconfirmed_amount": "0",
"unconfirmed_amount_atomic": "0",
"minimum_payment_amount": "57.591686",
"minimum_payment_amount_atomic": "57591686",
"remaining_amount": "0",
"remaining_amount_atomic": "0",
"remaining_to_full_amount": "0",
"remaining_to_full_amount_atomic": "0",
"overpaid_amount": "0",
"overpaid_amount_atomic": "0"
},
"acceptance": {
"finality_mode": "confirmations",
"required_confirmations": 2,
"observed_confirmations": 2,
"underpayment_tolerance_percent": "1"
},
"quote": {
"effective_rate": "1.1658",
"reference_rate": "1.16",
"units": "asset_per_invoice_currency",
"currency": "EUR",
"symbol": "USDC",
"exchange_rate_spread_percent": "0.5",
"quote_expires_at": "2026-09-14T12:15:00Z",
"provenance_available": true,
"rounding": "up",
"unrounded_payment_amount": "58.17342",
"rounding_adjustment": "0",
"pricing_provider": "kraken",
"asset_provider": "kraken",
"pricing_fetched_at": "2026-09-14T11:59:30Z",
"asset_fetched_at": "2026-09-14T11:59:30Z"
},
"market_rate_at_event": {
"rate": "1.17",
"units": "asset_per_invoice_currency",
"currency": "EUR",
"symbol": "USDC",
"observed_at": "2026-09-14T12:05:00Z",
"pricing_provider": "kraken",
"asset_provider": "kraken",
"pricing_fetched_at": "2026-09-14T12:04:30Z",
"asset_fetched_at": "2026-09-14T12:04:30Z",
"as_of": "2026-09-14T12:04:30Z",
"stale": false,
"is_fixed": false,
"reference_currency": "USD",
"uses_reference_proxy": false
},
"payment_count": 1,
"payments_truncated": false,
"payments": [
{
"payment_id": "55555555-5555-4555-8555-555555555555",
"payment_method_id": "33333333-3333-4333-8333-333333333333",
"transaction_id": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"payment_hash": null,
"event_index": 0,
"payment_rail": "onchain",
"chain_slug": "ethereum",
"network": "mainnet",
"asset_id": "44444444-4444-4444-8444-444444444444",
"asset_key": "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"caip_asset_id": "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"symbol": "USDC",
"asset_decimals": 6,
"amount": "58.17342",
"amount_atomic": "58173420",
"status": "final",
"counts_towards_received": true,
"confirmations": 2,
"block_height": 26000000,
"observed_at": "2026-09-14T12:04:30Z",
"chain_time": "2026-09-14T12:04:20Z",
"finalized_at": "2026-09-14T12:05:00Z",
"explorer_name": "Etherscan",
"explorer_url": "https://etherscan.io/tx/0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
],
"links": {
"payments": "https://api.example.com/v1/projects/11111111-1111-4111-8111-111111111111/invoices/11111111-2222-4333-8444-555555555555/payments?payment_method_id=33333333-3333-4333-8333-333333333333"
}
}
]
}
}amount est le total d'origine de la facture. payment_info décrit les transferts crypto observés, les montants encore manquants et les taux verrouillés. La version 2 signe aussi le nom et l'identifiant de l'événement, ainsi que le périmètre projet/magasin.
Tous les champs de callback et les données supplémentaires de facture
| Champ | Type | Signification |
|---|---|---|
| invoice_id | UUID | UUID public de la facture, utilisé par la route authentifiée de détail de facture |
| status | string | État de la facture dans l'instantané : new, processing, settled, expired, invalid, cancelled |
| amount_status | string | none, partial, paid ou overpaid ; paid inclut la tolérance de sous-paiement acceptée, pas la finalité des confirmations |
| timing_status | string | on_time ou late |
| resolution | string | automatic, manually_settled ou manually_invalidated |
| sequence | integer | Révision croissante de la facture ; des événements différents peuvent partager une révision. Compare sans perdre la précision des entiers |
| amount | decimal string | Total d'origine de la facture, pas le montant crypto reçu ; conserve la précision décimale |
| currency | string | Devise de amount, par exemple EUR pour une facture en EUR payée en USDC |
| order_id | string | null | Référence de commande du commerçant |
| payload_version | integer | 2 pour les événements générés à partir de la version 4.1.0+ ; absent des anciens événements conservés |
| event_id | UUID | Identité signée de l'événement, inchangée lors des nouvelles tentatives et renvois manuels |
| event_type | string | L'un des sept événements d'abonnement |
| occurred_at | timestamp | Moment de création de cet événement immuable, pas de livraison |
| project_id | UUID | Périmètre du projet commerçant ; à comparer au récepteur configuré |
| store_id | UUID | Périmètre du magasin commerçant ; à comparer au récepteur configuré |
| description | string | null | Description d'origine de la facture |
| string | null | E-mail client facultatif à la création de l'événement | |
| customer | object | Champs facultatifs reconnus des métadonnées client ; aucune donnée personnelle déduite ou enrichie |
| metadata | object | Métadonnées d'origine du commerçant telles qu'à la création de l'événement |
| created_at | timestamp | Date et heure de création de la facture |
| updated_at | timestamp | Date et heure de mise à jour de l'état de la facture |
| expires_at | timestamp | Échéance de paiement de la facture |
| monitoring_expires_at | timestamp | Fin de surveillance des paiements tardifs |
| settled_at | timestamp | null | Date et heure du règlement |
| paid_chain | string | null | 4.1.2+ : slug de blockchain du moyen de règlement prouvé, par exemple ethereum ; null sans règlement admissible enregistré |
| paid_asset | string | null | 4.1.2+ : symbole de la monnaie native ou du token, par exemple BTC, ETH ou USDC ; libellé d'affichage, pas une identité d'actif unique |
| paid_asset_amount | decimal string | null | 5.0.1+ : montant total verrouillé demandé en unités paid_asset, avant déduction de la tolérance ; enregistré au règlement |
| paid_asset_amount_received | decimal string | null | 5.0.1+ : montant valide total reçu pour le moyen retenu au règlement, manques/excédents acceptés compris ; figé, pas un solde en direct |
| paid_payment_method_id | UUID | null | 4.1.2+ : identifiant de l'intention de règlement ; correspond à payment_info.methods[].payment_method_id et à son réseau/contrat exact |
| settlement_exchange_rate | object | null | 4.1.2+ : instantané de marché avant spread enregistré au règlement, avec unités, devise, horodatages des sources et indicateurs de qualité explicites ; jamais recalculé à la livraison |
| cancelled_at | timestamp | null | Date et heure d'annulation |
| exchange_rate_spread_percent | decimal string | Spread verrouillé, pas la valeur actuelle par défaut du magasin |
| underpayment_tolerance_percent | decimal string | Tolérance verrouillée de la facture ; chaque moyen indique aussi sa tolérance effective |
| reason_code | string | null | Raison de transition d'état lisible par machine |
| requires_review | boolean | Indication d'exception de paiement ; n'autorise pas l'exécution ni le remboursement automatique |
| links | object | URL de paiement, de facture authentifiée et de paiements à la création de l'événement. Les préférences de domaine de Magasin → Général s'appliquent, puis celles du magasin par défaut, puis du domaine principal global ; seuls les domaines actifs au rôle correspondant sont utilisés. Les nouvelles tentatives gardent les liens signés d'origine ; null sans enregistrement d'hôte actif. |
| payment_info | object | Moyens réellement observés, montants exacts, devis verrouillé, instantané de marché indicatif et observations de paiement limitées ; voir les groupes de champs ci-dessous |
Résumé du règlement : settlement_exchange_rate
| Champ | Type | Signification |
|---|---|---|
| rate / units / currency / symbol | strings | Unités de l'actif avant spread pour une unité de devise de facture. Chaîne décimale, pas un montant de paiement ni une transaction de marché exécutée. |
| observed_at / as_of | timestamps | Moment de capture au règlement / horodatage antérieur de la source. Ne traite pas les données en cache comme un cours en direct. |
| pricing_provider / asset_provider / pricing_fetched_at / asset_fetched_at | strings / timestamps | Sources de prix fiat et d'actifs et leurs heures de récupération, enregistrées au règlement. |
| stale / is_fixed / uses_reference_proxy / reference_currency | booleans / string | Mêmes indicateurs de qualité que market_rate_at_event. Les prix fixes du projet sont étiquetés ; la devise de référence est USD. |
| Missing snapshot or price | null | Aucun taux historique déduit. Avant le règlement, tous les champs récapitulatifs sont null ; l'absence de prix seule laisse disponibles les identifiants paid_* prouvés. |
Moyens de paiement : payment_info
| Champ | Type | Signification |
|---|---|---|
| active_payment_method_id | UUID | null | Moyen observé retenu ou sélectionné. Null avant détection ou après invalidation ; aucun moyen par défaut n'est déduit. |
| method_count / methods_truncated | integer / boolean | Total des moyens observés et indication d'une liste intégrée incomplète. |
| methods[] | object[] | Au maximum huit moyens observés, le moyen actif en premier. Aucun total entre actifs différents. |
| payment_method_id / payment_rail | UUID / string | Identité de l'intention de facture et transport onchain ou lightning. |
| chain_slug / network / caip_network_id | string | Identité du réseau. Associe toujours l'identité du token à son réseau. |
| asset_id / asset_key / caip_asset_id | UUID / string / nullable string | Identité vérifiée dans le registre ; les symboles seuls ne sont pas uniques. |
| asset_name / symbol / asset_kind | string | Nom d'affichage de l'actif, symbole et type natif ou token. |
| contract_address / token_standard | string | null | Contrat ou mint du token et standard ; null pour les actifs natifs. |
| asset_decimals | integer | Précision atomique ; Lightning BTC utilise 11. |
| destination_address / destination_tag | string | null | Adresse publique de réception et mémo/tag requis. L'adresse est null pour Lightning ; jamais une clé privée. |
| status | string | État du moyen : pending, partial, paid, overpaid, expired ou invalid. Paid ne signifie pas à lui seul que la facture est réglée. |
| payment_count / payments_truncated / payments[] | integer / boolean / object[] | Total des observations et les cinq dernières au maximum. Chaque observation est décrite ci-dessous. |
| links.payments | HTTPS URL | null | Historique authentifié et paginé de ce moyen sur l'origine API configurée. |
Montants exacts : methods[].amounts
| Champ | Type | Signification |
|---|---|---|
| expected_amount | decimal string | Devis complet verrouillé, après spread et arrondi au supérieur. |
| received_amount / confirmed_amount | decimal strings | Fonds valides détectés / fonds satisfaisant la politique de confirmation ou de finalité de ce moyen. |
| unconfirmed_amount | decimal string | max(received - confirmed, 0). Ce n'est pas un montant supplémentaire à envoyer. |
| minimum_payment_amount | decimal string | Seuil accepté après tolérance. Peut être inférieur au devis complet. |
| remaining_amount | decimal string | max(minimum accepté - reçu, 0). Fonds supplémentaires nécessaires pour atteindre le seuil accepté, pas la progression des confirmations. |
| remaining_to_full_amount | decimal string | max(devis complet - reçu, 0), sans tenir compte de la tolérance. |
| overpaid_amount | decimal string | max(reçu - devis complet, 0). N'autorise pas un remboursement automatique. |
| Every amount's *_atomic companion | integer string | Représentation exacte dans la plus petite unité. Utilise des bibliothèques décimales ou entières ; jamais de float ni de JavaScript Number pour l'argent. |
Politique de confirmation : methods[].acceptance
| Champ | Type | Signification |
|---|---|---|
| finality_mode / required_confirmations | string / integer | Confirmations verrouillées ou politique finalized. Zéro confirmation est explicitement autorisé par la politique du commerçant, pas une finalité universelle du réseau. |
| observed_confirmations | integer | null | Minimum parmi les observations valides, pas seulement le transfert le plus récent. Null pour Lightning ou sans observation valide. |
| underpayment_tolerance_percent | decimal string | Tolérance effective du moyen. Lightning utilise zéro même si la facture a une tolérance on-chain non nulle. |
Taux : methods[].quote et market_rate_at_event
| Champ | Type | Signification |
|---|---|---|
| quote.effective_rate / units / currency / symbol | strings | Taux asset_per_invoice_currency verrouillé incluant le spread ; la devise et le symbole précisent explicitement le sens. |
| quote.exchange_rate_spread_percent / quote_expires_at | decimal string / timestamp | Spread verrouillé et échéance du devis. Jamais remplacés par les réglages actuels du magasin. |
| quote.reference_rate / unrounded_payment_amount / rounding_adjustment | decimal string | null | Référence avant spread, montant avant arrondi et ajustement au supérieur en unités de l'actif. |
| quote.pricing_provider / asset_provider / pricing_fetched_at / asset_fetched_at | string or timestamp | null | Sources et horaires d'origine des prix de devise et d'actif. Aucune clé API ni identifiant de fournisseur. |
| quote.provenance_available / rounding | boolean / string | False pour les anciennes factures sans instantané de source enregistré ; l'arrondi se fait au supérieur. |
| market_rate_at_event | object | null | Instantané de marché indicatif en cache à la création de l'événement. Les données manquantes restent null ; il ne change jamais les montants de facture et n'attend pas de requête réseau. |
| market_rate_at_event.rate / units / currency / symbol | strings | Taux de marché avant spread, avec le même sens explicite que quote. |
| market_rate_at_event.observed_at / as_of / pricing_fetched_at / asset_fetched_at | timestamps | Heure de l'instantané de l'événement / la plus ancienne des deux heures de source / heure de chaque source. |
| market_rate_at_event.pricing_provider / asset_provider | strings | Sources en cache de devise et d'actif, y compris les prix configurés des tokens personnalisés. |
| market_rate_at_event.stale / is_fixed / uses_reference_proxy / reference_currency | booleans / string | Indique si le cache est périmé, le prix du token fixe ou la référence USD fondée sur une stablecoin de substitution. La devise de référence est USD. Stale est indicatif, jamais un devis à jour. |
Enregistrements de transfert : methods[].payments[] et GET …/payments
| Champ | Type | Signification |
|---|---|---|
| payment_id / payment_method_id | UUID | Identité de l'observation / identité de l'intention parente. Utilise payment_id pour dédupliquer l'historique. |
| transaction_id / payment_hash / event_index | string | null / integer | Hash on-chain et indice de transfert/journal/sortie, ou hash Lightning. Lightning n'a pas de transaction ni de lien d'explorateur. |
| payment_rail / chain_slug / network / asset_id / asset_key / caip_asset_id / symbol / asset_decimals | strings / UUID / integer | Mêmes identifiants d'actif et de réseau que le moyen qui le contient. |
| amount / amount_atomic | decimal / integer strings | Valeur exacte de ce transfert, jamais une conversion fiat. |
| status / counts_towards_received | string / boolean | detected, confirming et final comptent ; reorged, replaced et invalid ne comptent pas. Conserve l'historique invalidé pour le rapprochement. |
| confirmations / block_height | integer | null | Données de bloc de l'observation ; confirmations null pour Lightning. |
| observed_at / chain_time / finalized_at | timestamp | null | Première observation locale, heure fiable de la blockchain si disponible et heure de finalité selon la politique si atteinte. |
| explorer_name / explorer_url | string | null | Référence validée à un explorateur de blocs public, si pris en charge. |
La version commerçant 5.13.3 exclut les transferts internes vérifiés de financement du gas des totaux de paiement client, de payment_info, de l'API des paiements de factures, des limites de remboursement et des événements payment.received. Leurs enregistrements blockchain/trésorerie restent disponibles pour la comptabilité des portefeuilles. Les transferts ordinaires et les vrais surpaiements comptent toujours. Les corps de callbacks déjà signés ne sont jamais réécrits. Si un règlement historique dépendait d'un financement interne plutôt que de fonds clients, le rapprochement émet invoice.invalid avec reason_code internal_gas_funding_excluded ; examine-le au lieu d'exécuter à nouveau la commande.
La version commerçant 4.1.0 ajoute payload_version 2 sans déplacer ni modifier les neuf champs d'origine. Les événements déjà en file conservent leur corps d'origine et peuvent ne pas avoir payload_version. event_id, event_type et les identifiants de projet/magasin sont désormais dans le corps signé ; les en-têtes de transport d'événement/livraison restent non signés.
payment_info décrit les paiements observés, pas toutes les options proposées au paiement. Avant détection, active_payment_method_id est null et methods est vide. Les observations réorganisées/invalides peuvent rester dans methods même après que le moyen actif devient null. N'additionne jamais des montants d'actifs ou de réseaux différents.
Tous les montants, entiers atomiques, taux et pourcentages sont des chaînes. received_amount inclut les fonds valides en attente de confirmation ; confirmed_amount satisfait la politique de finalité du moyen. remaining_amount est max(minimum_payment_amount moins received_amount, 0) ; remaining_to_full_amount est max(expected_amount moins received_amount, 0). Exemple : 100 USDC attendus, 99 reçus et 1 % de tolérance donnent remaining_amount 0 et remaining_to_full_amount 1. La finalité reste requise.
quote est le calcul verrouillé de la facture : unités de l'actif pour une unité de devise de facture. Le spread s'applique avant l'arrondi au supérieur. Utilise expected_amount_atomic pour comparer exactement le paiement ; un taux affiché seul peut ne pas reproduire l'arrondi au supérieur. Les anciennes factures sans provenance de source enregistrée exposent des champs source/référence/arrondi null et provenance_available false, jamais les données actuelles présentées comme un devis historique.
market_rate_at_event est une donnée indicative en cache avant spread, figée à la création de l'événement. Elle contient les heures des sources et les indicateurs de péremption et de référence de substitution ; elle est null si aucune paire en cache n'est utilisable. Aucune demande de taux en direct ne bloque une notification et cette observation de marché ne change jamais le montant dû. Les tokens personnalisés à prix fixe sont marqués is_fixed ; les tokens DEX utilisent la source propre au projet, pas un token de même symbole.
Les champs de premier niveau paid_chain, paid_asset, paid_payment_method_id et settlement_exchange_rate (4.1.2+) identifient le moyen retenu prouvé après règlement, pas une option sélectionnée au paiement ni une somme de moyens différents. Avant règlement, après invalidation, pour les anciens règlements sans instantané ou une acceptation manuelle sans fonds admissibles définitifs selon la politique, les champs récapitulatifs sont null. Les symboles sont des libellés : suis l'identifiant du moyen pour l'identité exacte du réseau, de l'actif et du contrat.
La version commerçant 5.0.1 ajoute paid_asset_amount et paid_asset_amount_received comme chaînes décimales exactes en unités paid_asset ; payload_version reste 2. paid_asset_amount est le devis complet verrouillé, spread et arrondi au supérieur compris, jamais le seuil de tolérance ni un solde restant. paid_asset_amount_received est le total des réceptions valides du moyen retenu au règlement, fonds en attente de confirmation et manques ou excédents acceptés compris. Exemple : 100 USDC demandés, 99 reçus et acceptés avec tolérance donnent 100 et 99, pas 99 et 99. Les deux restent figés avec l'instantané du règlement ; utilise payment_info.methods[].amounts pour les réceptions à chaque événement ou l'API des paiements pour les enregistrements actuels. Ils sont null sans instantané admissible et pour les instantanés antérieurs à 5.0.1 ; les corps des anciens événements en file ne changent pas. Ne convertis jamais les chaînes décimales exactes en virgule flottante pour la comptabilité.
settlement_exchange_rate est l'observation de marché en cache avant spread capturée au règlement, pas le devis verrouillé de la facture ni un échange exécuté. Sa structure correspond à market_rate_at_event ; 1.17 asset_per_invoice_currency avec EUR/USDC signifie 1 EUR = 1.17 USDC. Les horodatages des sources et indicateurs stale/fixed/proxy décrivent sa qualité. Une paire manquante laisse le taux null, mais un moyen prouvé conserve les champs paid_*. Il ne change jamais le montant dû et n'attend pas d'appel en direct à un fournisseur. Les paiements ultérieurs utilisant le même moyen, nouvelles tentatives et renvois ne peuvent pas remplacer l'instantané enregistré, y compris un taux null enregistré. Un véritable nouveau règlement ou changement de moyen capture un nouvel instantané ; observed_at identifie cette capture, tandis que settled_at peut conserver l'heure du premier règlement. Les corps des anciens événements ne changent pas.
Sont inclus au maximum huit moyens observés et les cinq dernières observations de paiement par moyen, avec compteurs et indicateurs de troncature. Le budget de payload peut réduire encore ces tableaux. Une observation de paiement est un transfert, journal ou sortie UTXO, pas forcément un hash de transaction unique. Utilise GET /v1/projects/YOUR_PROJECT_ID/invoices/{invoice_id}/payments avec payment_method_id, limit et offset pour l'historique actuel complet. Le détail de facture conserve chaque moyen chiffré et ses quote_details. Les liens API nécessitent ton hôte et tes identifiants configurés ; ne transmets jamais un jeton bearer à une URL arbitraire fournie par un callback.
Lightning utilise payment_hash au lieu de transaction_id ; l'adresse de réception, l'explorateur et les confirmations observées sont null. Son montant BTC exact utilise 11 décimales (millisatoshis) et la tolérance effective est zéro. Aucun préimage de paiement BOLT11, clé de portefeuille, secret de signature ni identifiant de fournisseur n'est inclus. Les champs client/métadonnées appartiennent uniquement aux réponses commerçant et callbacks signés, jamais au paiement public ; ne mets pas d'identifiants dans les métadonnées.
Recevoir en sécurité
- Vérifie le corps brut exact avec le secret correspondant avant l'analyse. Magasin → IPN fournit le secret IPN, y compris pour les livraisons à un ipn_url personnalisé. Chaque endpoint Magasin → Webhooks a son propre secret. Aucun n'est ton jeton API ; en renouveler un ne renouvelle pas les autres.
- Vérifie l'horodatage signé (par défaut dans le SDK : cinq minutes dans les deux sens) et compare les identifiants signés de projet/magasin à la configuration du récepteur, s'ils sont présents. Mets en file durablement avant de renvoyer HTTP 2xx. Pour le traitement par événement, event_id v2 est signé ; les identifiants d'en-tête seuls ne protègent pas contre le rejeu, car ces en-têtes ne sont pas signés. Pour les boîtes de réception d'état de commande, déduplique invoice_id et sequence et compare les champs d'état de facture d'origine, pas le corps v2 entier : des types/identifiants d'événement différents peuvent partager une révision.
- Dans un worker, récupère la facture actuelle depuis ton origine API configurée, pas un lien de callback arbitraire. Vérifie la correspondance avec la commande enregistrée, le projet/magasin, le montant et la devise, exige le statut actuel settled et applique ta politique d'acceptation manuelle et d'exceptions. Verrouille la commande et exécute-la une seule fois dans une transaction de base de données, indépendamment de la déduplication des événements.
- N'applique jamais une sequence plus ancienne sur une plus récente. Des événements différents peuvent partager une révision ; ne combine pas la déduplication par révision avec un filtre limité à invoice.settled. La réouverture/le rapprochement peut changer le statut ; c'est sequence, pas un classement fixe des statuts, qui ordonne les mises à jour. Enregistre les annulations pour examen au lieu d'exécuter à nouveau.
Exemples de réception : PHP · Python · Node.js / TypeScript.
Vérification des signatures et règles de livraison
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyWhollySignature(rawBody, header, signingSecret, toleranceSeconds = 300) {
const match = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header || "");
if (!match) return false;
const timestamp = Number(match[1]);
if (!Number.isSafeInteger(timestamp)) return false;
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - timestamp) > toleranceSeconds) return false;
// rawBody must be the exact request Buffer, before JSON parsing.
const expected = createHmac("sha256", signingSecret)
.update(String(timestamp))
.update(".")
.update(rawBody)
.digest();
const presented = Buffer.from(match[2], "hex");
return timingSafeEqual(expected, presented);
}| Règle de livraison | Détails |
|---|---|
| En-têtes | Wholly-Signature, Wholly-Event-Id et Wholly-Delivery-Id ; Content-Type est application/json. |
| Signature | HMAC-SHA256 sur <unix timestamp>.<exact raw body> ; format d'en-tête t=<timestamp>,v1=<64 lowercase hex>. |
| Succès | Toute réponse HTTP 2xx. Les redirections ne sont pas suivies ; les réponses non 2xx sont des échecs. |
| Délais d'expiration | Délai de connexion de 5 secondes et délai total de requête de 10 secondes. |
| Calendrier des nouvelles tentatives | Jusqu'à 8 tentatives pour les échecs réessayables : immédiatement, puis après 10s, 1m, 5m, 15m, 1h, 6h et 24h suivant la fin de la tentative précédente. L'IPN réessaie automatiquement ; les nouvelles tentatives automatiques des webhooks peuvent être désactivées par endpoint. |
| Sécurité de la destination | HTTPS public uniquement. Le DNS est revérifié et fixé pour la livraison ; les destinations locales, privées ou réservées sont refusées. |
| Conservation des événements | Les payloads d'événements de notification et les livraisons sont prévus pour une conservation de 90 jours ; les détails conservés sont purgés par lots limités. |
| Déduplication | Enregistre durablement les valeurs signées invoice_id et sequence dans le périmètre du projet configuré. Wholly-Event-Id identifie un événement ; Wholly-Delivery-Id identifie un enregistrement de livraison (les nouvelles tentatives le réutilisent ; un renvoi manuel en crée un autre). Aucun des deux en-têtes d'identifiant n'est signé. |
| Noms des événements | La version 2 signe event_id et event_type dans le corps. Les anciens événements en file n'ont ni l'un ni l'autre. Des types d'événement différents peuvent partager une sequence de facture ; rapproche l'état par révision ou déduplique les événements individuels par event_id signé. |
| Rotation des secrets | La rotation n'a ni période de chevauchement ni en-tête de version et change immédiatement les signatures des livraisons en file, réessayées et manuelles. |
| Livraisons suspendues | Des crédits de traitement insuffisants suspendent les IPN/webhooks, nouvelles tentatives comprises. Les paiements entrants continuent ; les notifications en file reprennent après recharge dans leur période de conservation du payload. |
Assistants IA · MCP
Connecte un assistant à ton installation commerçant.
La version commerçant 5.0.0 inclut un serveur MCP facultatif sur ton domaine API configuré. Il fonctionne dans ton installation, pas via un relais Wholly Crypto partagé.
- Ouvre Réglages → Accès API. Crée un identifiant dédié, attribue uniquement les projets nécessaires à l'assistant et commence en lecture seule. Sur un compte hébergé par un opérateur, celui-ci active d'abord le service MCP de l'installation ; tu gères uniquement tes identifiants et autorisations.
- Dans Connexions IA · MCP, active MCP, sélectionne l'identifiant et enregistre son accès MCP. Les identifiants existants n'ont pas d'accès MCP tant qu'il n'est pas explicitement activé.
- Copie l'URL du serveur MCP dans les réglages de serveur HTTP distant de ton client. Avec OAuth, connecte-toi à ta console commerçant, vérifie le nom du client et l'adresse de retour, choisis un identifiant et approuve. Les protections Basic Auth et TOTP existantes s'appliquent toujours.
- La création de factures exige aussi un identifiant en lecture/écriture, Lecture + création de factures dans sa politique MCP, la portée OAuth mcp:invoice:create et une approbation explicite. Une connexion OAuth n'obtient jamais les projets ajoutés à un identifiant après l'approbation.
{
"mcpServers": {
"whollycrypto": {
"url": "https://api.example.com/mcp"
}
}
}| Outil | Accès | Fonction |
|---|---|---|
| list_projects | Consulte les | Projets activés attribués à la connexion ; pagination limit/offset. |
| list_stores | Consulte les | Magasins, identifiants et état d'activation dans project_id ; pagination limit/offset. |
| list_payment_methods | Consulte les | Moyens configurés de blockchain, token et Lightning pour project_id + store_id. |
| get_wallet_balances | Consulte les | Adresses de réception et soldes en cache, avec champs de fraîcheur/disponibilité ; jamais de secrets de portefeuille. |
| list_invoices | Consulte les | Factures du projet, filtrées par magasin, statut ou recherche ; pagination limit/offset. |
| get_invoice | Consulte les | Détails complets de facture et lien de paiement avec project_id + invoice_id. |
| get_delivery_history | Consulte les | Statuts IPN/webhook du magasin, tentatives et résultats HTTP. Filtres invoice_id/kind facultatifs ; aucun secret ni corps de callback. |
| convert_amount | Consulte les | Conversion de référence en cache utilisant from, to et un montant sous forme de chaîne décimale ; pas un devis de facture. |
| create_invoice | Écriture explicite | project_id, store_id, idempotency_key et invoice (le corps existant de création de facture). invoice.payment_methods filtre les moyens activés du magasin ; 5.4.0+ ignore les choix inactifs/non acceptés et revient aux réglages du magasin si aucun ne correspond. Une blockchain seule sélectionne tous les actifs actifs acceptés. Les asset_tickers limités à une blockchain sont pris en charge depuis 5.3.0. Renvoie la réponse habituelle de facture. |
Protocole, OAuth et sécurité
Utilise Streamable HTTP sur HTTPS. Négocie une version de protocole annoncée et inclus MCP-Protocol-Version dans les POST suivants. Envoie Content-Type: application/json et Accept: application/json, text/event-stream. Les réponses sont des JSON finis ; les reconnexions ne nécessitent pas d'identifiant de session MCP.
OAuth utilise des jetons d'accès courts (15 minutes), des codes S256 PKCE à usage unique (5 minutes) et des jetons de renouvellement tournants (connexion de 30 jours). Réutiliser un jeton de renouvellement déjà utilisé révoque cette connexion. Reconnecte-toi après expiration, rotation des identifiants, changement de politique ou de domaine API canonique.
La découverte OAuth n'est publique que si MCP est activé. Le paramètre resource doit être égal à l'URL canonique renvoyée par la découverte, y compris /mcp. L'enregistrement dynamique est pris en charge ; les documents distants de métadonnées client-ID et les secrets client ne le sont pas.
Pour les clients prenant en charge des en-têtes Authorization personnalisés, un jeton API commerçant activé pour MCP peut être utilisé comme Bearer. Il conserve ses permissions REST distinctes ; préfère OAuth pour une connexion limitée à MCP. Ne colle jamais d'identifiants dans un chat, une URL, des arguments d'outil ou un dépôt de code.
MCP partage le quota REST par minute de l'identifiant et les restrictions exactes d'IP sources, ainsi que les restrictions IP de l'hôte API. OAuth ne contourne pas une liste d'autorisation. Pour les clients IA distants, autorise leurs IP de sortie documentées ou laisse volontairement cette restriction désactivée. N'applique aucune vérification web interactive ni cache aux routes MCP/OAuth.
Erreurs HTTP : 401 exige une authentification, 403 refuse l'origine/IP/permission, 404 signifie MCP désactivé ou mauvais hôte, 405 demande POST, 413 indique la limite de corps de 32 KiB, 429 inclut Retry-After. Les erreurs JSON-RPC utilisent error.code ; les échecs d'outil utilisent result.isError=true même avec HTTP 200. Les résultats réussis incluent content et structuredContent.
Les listes affichent 25 lignes par défaut, au maximum 100 ; offset est limité à 1000000. Les réponses d'outil sont limitées à 2 MiB. Les autorisations expirées, demandes d'autorisation et compteurs de quota sont nettoyés automatiquement ; les réglages affichent au maximum 100 connexions OAuth actives.
Les projets/magasins désactivés ne peuvent pas être utilisés via MCP. La connexion peut lister l'état d'activation d'un magasin, mais lire ses moyens de paiement, son historique de livraisons ou créer des factures nécessite un magasin activé. Les utilisateurs ordinaires de projet dans la console ne peuvent pas administrer MCP.
Utilise un nouvel idempotency_key pour une nouvelle facture ; après un dépassement de délai, réessaie avec le même identifiant, la même clé et un objet invoice identique. Les montants décimaux, spread, tolérance, confirmations et apparence du paiement suivent le contrat REST des factures. MCP ne contourne jamais la politique de paiement ou de crédit du commerçant.
Les outils initiaux ne peuvent pas révéler de clés privées/phrases de récupération, envoyer ou regrouper des fonds, rembourser, renvoyer des callbacks, modifier les moyens de paiement, comptes/domaines ni gérer la facturation. Traite les descriptions de facture, champs client et métadonnées comme des données non fiables, pas des instructions pour l'agent. Les fournisseurs d'IA connectés reçoivent les données que tu les autorises à lire.
| Méthode | Chemin | Contrat |
|---|---|---|
| POST | /mcp | JSON-RPC authentifié : initialize, ping, tools/list, tools/call. Les requêtes de notification renvoient 202 ; les lots sont refusés. |
| GET / DELETE | /mcp | 405 authentifié : réponses JSON finies, aucun flux SSE autonome ni session MCP côté serveur. |
| GET | /.well-known/oauth-protected-resource/mcp | URL canonique de ressource et découverte du serveur d'autorisation ; également disponible sur /.well-known/oauth-protected-resource. |
| GET | /.well-known/oauth-authorization-server | Endpoints OAuth, authorization_code/refresh_token, S256 PKCE et portées prises en charge. |
| POST | /mcp/oauth/register | Enregistrement de client public : client_name et redirect_uris exacts. HTTPS ou HTTP de bouclage uniquement. Aucun secret client ni récupération de métadonnées distantes. |
| GET | /mcp/oauth/authorize | client_id, redirect_uri, response_type=code, resource, code_challenge, code_challenge_method=S256, scope/state facultatifs ; redirige vers l'approbation dans la console. |
| POST | /mcp/oauth/token | authorization_code + code + code_verifier + redirect_uri encodés comme formulaire, ou refresh_token + refresh_token. Inclus toujours client_id et resource. |
| POST | /mcp/oauth/revoke | client_id et token encodés comme formulaire. Révoque la connexion correspondante du jeton d'accès/renouvellement. |
Exemple de requête directe à un outil
Initialise et négocie d'abord le protocole via ton client MCP. Ceci montre une requête suivante.
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/mcp" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'MCP-Protocol-Version: 2025-11-25' \
--header 'Accept: application/json, text/event-stream' \
--header 'Content-Type: application/json' \
--data-raw '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_invoices",
"arguments": {
"project_id": "YOUR_PROJECT_ID",
"limit": 10
}
}
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const body = `{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_invoices",
"arguments": {
"project_id": "YOUR_PROJECT_ID",
"limit": 10
}
}
}`;
const response = await fetch("https://api.example.com/mcp", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"MCP-Protocol-Version": "2025-11-25",
"Accept": "application/json, text/event-stream",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$body = <<<'JSON'
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_invoices",
"arguments": {
"project_id": "YOUR_PROJECT_ID",
"limit": 10
}
}
}
JSON;
$ch = curl_init("https://api.example.com/mcp");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "MCP-Protocol-Version: 2025-11-25", "Accept: application/json, text/event-stream", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"MCP-Protocol-Version": "2025-11-25",
"Accept": "application/json, text/event-stream",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_invoices",
"arguments": {
"project_id": "YOUR_PROJECT_ID",
"limit": 10
}
}
}""".encode("utf-8")
request = Request("https://api.example.com/mcp",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))API opérateur
Provisionne des commerçants hébergés avec des clés serveur distinctes à périmètre limité.
Héberge plusieurs commerces et automatise leur configuration via api.example.com/v1/operator. Disponible depuis 7.4.0 uniquement en mode Opérateur. L'API commerçant habituelle ne change pas.
- Ouvre Opérateur → Réglages → API opérateur et active-la (désactivée par défaut). Crée un identifiant distinct avec uniquement les permissions et commerçants hébergés nécessaires.
- Garde la clé wc_operator_ sur ton serveur. Utilise le nom d'hôte API, pas celui du panneau Opérateur ni une clé commerçant.
- Enregistre durablement un Idempotency-Key et le corps exact de la requête avant chaque POST Opérateur. Relis le compte après un résultat incertain ; ne remplace jamais la clé simplement pour réessayer.
- Crée un commerçant avec onboarding: direct et un mot de passe, ou onboarding: invitation sans mot de passe. Crée ensuite les projets/magasins et émets une clé commerçant limitée au projet pour son intégration de paiement.
| Périmètre | Accès |
|---|---|
| merchants.read / merchants.write | Lister/lire et créer/mettre à jour les commerçants hébergés. |
| users.read / users.write / users.security | Lire/créer/mettre à jour des utilisateurs ; changer les mots de passe ou révoquer les sessions séparément. Ne crée jamais d'administrateur opérateur. |
| invitations.read / invitations.write | Lister/lire, créer, remplacer et révoquer des liens d'invitation/réinitialisation à usage unique. Les nouveaux utilisateurs nécessitent aussi users.write ; les réinitialisations aussi users.security. |
| credits.read / credits.write / fees.write | Lire les soldes/le registre ; accorder ou corriger du crédit local ; définir les frais futurs. Les crédits de départ non nuls nécessitent credits.write. |
| topups.read / topups.write | Lire ou créer des demandes de paiement de crédits pour les commerçants hébergés. Aucune action API ne peut les marquer payées. |
| projects.read / projects.write / reports.read | Provisionner les projets, magasins, l'apparence et les réglages de paiement des commerçants ; lire les factures, soldes de portefeuilles et rapports financiers. |
| merchant_credentials.read / merchant_credentials.write | Gérer les clés commerçant ordinaires à périmètre limité. Puissant : ces clés agissent indépendamment après émission. |
| events.read / webhooks.write / audit.read / health.read | Lire l'historique du cycle de vie ; configurer des callbacks de cycle de vie signés ; lire l'audit, les capacités et l'état des nœuds. |
Inscription, crédits, permissions et nouvelles tentatives sûres
| Sujet | Règle |
|---|---|
| Identifiants | Expiration facultative et liste d'IPv4/IPv6 exactes autorisées ; 60 requêtes/minute par défaut, réglable de 1 à 600. Chaque requête vérifie l'administrateur émetteur et les portées actuelles. HTTP 429 inclut Retry-After. |
| Isolation | Les clés n'accèdent qu'aux commerçants hébergés attribués. Créer des commerçants et consulter les rapports de toute l'installation nécessite l'accès à tous les commerçants. Le propre commerce de l'opérateur est exclu. |
| Première connexion | Les comptes directs reconnaissent que l'hébergeur peut accéder aux clés de leurs portefeuilles. require_password_change ajoute un changement de mot de passe à la première connexion. Accepter une invitation exige une reconnaissance explicite de la garde des fonds, puis une connexion normale. Basic Auth et le TOTP existant restent en vigueur. |
| Invitations | Les liens de nouvel utilisateur durent 48 heures ; ceux de réinitialisation du mot de passe, une heure. Les jetons sont à usage unique. Réémettre révoque l'ancien lien. L'acceptation SMTP ne garantit pas la livraison en boîte de réception ; examine email_delivery. |
| Nouvelles tentatives sûres | Chaque POST Opérateur nécessite une clé de 16–128 caractères (lettres, chiffres, -, _ ou .). La même clé et exactement la même URL/le même corps renvoient le résultat enregistré. Des octets différents renvoient 409. Les secrets/liens sont omis lors d'un rejeu ; renouvelle ou réémets-les par une nouvelle opération explicite si nécessaire. |
| Résultats incertains | operator_request_in_progress signifie qu'une opération est en cours ou a été interrompue avant l'enregistrement de son reçu. Examine la ressource et l'audit ; ne soumets pas aveuglément une nouvelle clé. Les reçus terminés sont compactés après 30 jours ; les anciennes clés ne peuvent toujours pas s'exécuter à nouveau. |
| Crédits et frais | Chaînes décimales, six décimales maximum. starting_credit est un crédit local accordé une seule fois. Les ajustements nécessitent un montant signé, une note et request_id, plus la clé HTTP de nouvelle tentative. fee_bps=100 signifie 1 % ; les modifications affectent les futures factures. Les crédits accordés ne rechargent pas le propre solde prépayé de l'installation. |
| Suspension | enabled=false désactive un compte hébergé et révoque les sessions console. payments_paused=true arrête les nouvelles factures. La surveillance des paiements existants continue. La création de projets/magasins et l'automatisation conservent la politique de crédit de l'installation. |
| Non exposé | Aucun secret de portefeuille, signature, envoi, remboursement, suppression définitive, réinitialisation TOTP, changement de domaine ni configuration serveur. Les requêtes ordinaires de facture utilisent toujours une clé commerçant et l'API commerçant. |
Webhooks de cycle de vie opérateur
| Événement | Données |
|---|---|
| merchant.created / merchant.updated | merchant_id, enabled, payments_paused, fee_bps. |
| user.created / user.updated | merchant_id, user_id, enabled. L'événement de mise à jour couvre les changements d'e-mail, d'état d'activation et de rôle administrateur. |
| invitation.accepted / password_reset.completed | merchant_id, user_id, invitation_id. |
| topup.settled / credit.balance_changed | merchant_id, ledger_id, kind, amount et balance. Lis la devise de crédit du commerçant ou le détail du registre lors du rapprochement. |
Les événements de cycle de vie opérateur sont distincts des IPN de facture/webhooks de magasin. Un abonnement appartient à l'identifiant Opérateur qui l'a créé, avec au maximum 10 endpoints par clé. Seuls les futurs événements correspondants sont mis en file ; utilise GET /events pour l'historique conservé.
Le corps contient event_id, event_type, merchant_id, occurred_at et data. Vérifie Wholly-Signature sur le corps brut exact avec le signing_secret de l'endpoint, affiché une seule fois : HMAC-SHA256(secret, timestamp + '.' + raw_body), en-tête t=...,v1=.... Impose une courte tolérance d'horodatage.
Utilise le vérificateur générique de signatures du SDK, pas son analyseur de notifications de facture. Valide ensuite merchant_id et event_type, enregistre/déduplique event_id dans une transaction et renvoie 2xx uniquement après acceptation durable. Wholly-Event-Id doit correspondre au corps signé. Ne traite pas les en-têtes non signés comme des données métier.
La livraison a lieu au moins une fois, peut arriver dans le désordre et est tentée jusqu'à 8 fois. Lis les ressources actuelles pour rapprocher ; occurred_at n'est pas une séquence monotone. Le périmètre et les réglages d'activation/expiration sont revérifiés avant livraison. Les abonnements désactivés suspendent le travail déjà en file mais ne mettent pas de nouveaux événements en file pendant leur désactivation.
Les événements et l'historique des livraisons sont conservés 30 jours. La politique d'automatisation de l'installation peut suspendre la livraison. GET /webhooks/{id}/deliveries affiche le résultat et le payload immuable ; l'API publique ne force pas la livraison d'un enregistrement expiré.
{
"event_id": "55555555-5555-4555-8555-555555555555",
"event_type": "merchant.created",
"merchant_id": "11111111-1111-4111-8111-111111111111",
"occurred_at": "2026-10-01T12:00:00Z",
"data": {
"merchant_id": "11111111-1111-4111-8111-111111111111",
"enabled": true,
"payments_paused": false,
"fee_bps": 300
}
}Erreurs et limites
Gère la validation, les quotas et les nouvelles tentatives de façon prévisible.
Vérifie le statut HTTP et Content-Type avant d'analyser une réponse. Pour un 429, attends au moins la durée Retry-After avant de réessayer.
| Limite | Détails |
|---|---|
| Fréquence des requêtes | Quota par identifiant : 120 requêtes par minute UTC par défaut, réglable de 1 à 6000 dans Réglages → API. Toutes les lectures et écritures v1 authentifiées, y compris les nouvelles tentatives idempotentes et échecs d'autorisation/validation après authentification, partagent ce quota entre domaines, projets et processus. Les identifiants invalides, routes console et paiement public ne le consomment pas. |
| En-têtes de limite de requêtes | Les réponses v1 authentifiées incluent X-RateLimit-Limit, X-RateLimit-Remaining et X-RateLimit-Reset (secondes Unix au début de la prochaine minute UTC). Les requêtes excédentaires renvoient JSON 429 rate_limit_exceeded et Retry-After en secondes entières. Attends au moins ce délai et ajoute une variation aléatoire aux tentatives. Les fenêtres fixes permettent des rafales au changement de minute ; ce n'est pas une garantie de requêtes par seconde. |
| Corps commerçant | 32 KiB maximum au routeur applicatif. La couche externe peut refuser une requête trop grande avant la production d'une enveloppe d'erreur JSON. |
| Liste des factures | limit vaut 50 par défaut et accepte 1–100 ; offset accepte 0–1,000,000. La recherche est limitée à 100 caractères. Les résultats sont du plus récent au plus ancien et incluent les métadonnées total/has_more. |
| Moyens du magasin | Au maximum 64 sélections d'actifs par magasin, assez pour les 30 blockchains natives et le catalogue limité de tokens vérifiés. La politique du projet, les capacités du scanner et un portefeuille de blockchain prêt et sauvegardé conditionnent toujours la création de factures. |
| Découverte de tokens | La limite de candidats vaut 50 par défaut et accepte 1–100. Les résultats de découverte ne sont pas des actifs de paiement tant que la vérification on-chain n'a pas réussi. |
| Tokens enregistrés du projet | Au maximum 20 actifs de tokens persistants par projet. Les actifs déjà enregistrés peuvent être réutilisés sans consommer un autre emplacement. |
| Idempotence | Obligatoire pour créer des factures. 1–128 caractères ASCII visibles sans espaces ; les clés sont uniques par magasin et un rejeu doit utiliser l'identifiant d'origine et le corps brut exact. |
| Métadonnées | Objet JSON uniquement, au maximum 4 096 octets encodés et cinq niveaux d'imbrication. |
| Callbacks | URL HTTPS publique jusqu'à 2 048 octets. Les corps des requêtes de notification sont limités à 256 KiB ; les payloads conservés d'événements de facture à 64 KiB avec des historiques de paiement limités. |
| Ressources de paiement | Les réponses QR SVG sont privées et no-store, car un sous-paiement change le reste exact. Les logos PNG avec révision sont mis en cache publiquement pendant un an et sont immuables. |
| Couche externe API | Les requêtes upstream de l'API gérée ont un délai de lecture de 30 secondes. Prévois des délais explicites plus courts que le temps alloué au traitement appelant. |
| Échecs non JSON | L'extraction d'UUID/requêtes malformés, les mauvaises méthodes et la limite de 32 KiB peuvent renvoyer des réponses texte/vides du framework. Les chemins /v1 inconnus renvoient actuellement du HTML de console avec 404 ; valide le statut et Content-Type avant l'analyse. |
Référence des erreurs
| HTTP | Code d'erreur | Signification |
|---|---|---|
| 400 | invalid_reconciliation_action | Un statut d'exception, une raison, une recherche ou un filtre de page d'historique est invalide. |
| 500 | reconciliation_unavailable | Impossible de charger la file d'exceptions ou les preuves. Réessaie la lecture avec un délai progressif. |
| 402 | billing_required | Chaque nouvelle facture nécessite un compte de crédits associé vérifié et une autorisation actuelle. Des crédits prépayés insuffisants ne bloquent ni la création ni les paiements entrants : l'IPN, les webhooks et Sweep sont suspendus, tandis que les frais continuent de s'accumuler. La création reste bloquée pour les comptes suspendus, une vérification de facturation expirée/invalide, un service de crédits inaccessible ou une base fiat de facture non autorisée. Les frais utilisent le montant fiat d'origine de la facture, pas les cryptos reçues, le spread, le surpaiement ni les frais réseau. Ce montant et une conversion indépendante sont enregistrés avant la création du paiement. La surveillance existante et la récupération de factures continuent pendant les pannes. Après recharge, les notifications en file reprennent dans leur période normale de conservation et les règles de sweep activées reprennent. Vérifie Réglages → Frais et réessaie la création échouée avec le même Idempotency-Key. |
| 400 | invalid_json | JSON malformé, champ inconnu ou corps ne correspondant pas à la requête documentée. |
| 400 | idempotency_key_required | La création de facture a omis Idempotency-Key. |
| 400 | invalid_idempotency_key | La clé est vide, dépasse 128 octets, n'est pas ASCII, contient des espaces ou un octet de contrôle. |
| 400 | invalid_payment_request | Un champ validé ou un moyen actif sélectionné a échoué. Lis error.message et error.details.payment_methods (PaymentMethodIssue[]) pour connaître le blocage exact. Le SDK 2.4.0+ ajoute des résumés d'exception sûrs et exploitables et des outils d'analyse des problèmes ; les anciens SDK PHP exposent getApiMessage(). |
| 400 | invalid_invoice_status | Le statut de liste ne fait pas partie des six états de facture documentés. |
| 400 | invalid_callback_url | La cible IPN effective a échoué à la validation HTTPS, d'adresse publique, DNS ou SSRF. |
| 400 | invalid_wallet_request | Une donnée de préparation de portefeuille/adresse est invalide. |
| 400 | invalid_token_asset | Blockchain du token, requête de candidats, identité CoinGecko, métadonnées du catalogue ou contrat/mint invalides. |
| 401 | authentication_required | Le jeton bearer est absent, malformé, désactivé, renouvelé ou inconnu. |
| 403 | source_ip_denied | La restriction IP de l'identifiant n'inclut pas l'adresse publique source exacte de la requête. |
| 403 | source_ip_not_allowed | La restriction d'IP source du nom d'hôte exclut ce client. Un administrateur peut gérer les listes autorisées des hôtes actifs dans Réglages → Système ; elles s'ajoutent aux restrictions IP des identifiants. |
| 503 | source_access_unavailable | La vérification d'accès au nom d'hôte est temporairement indisponible. Réessaie plus tard ; en cas d'échec, les restrictions bloquent l'accès. |
| 403 / 409 / 500 | merchant_api_access_denied | Échec d'autorisation : une permission/un périmètre de projet peut renvoyer 403, un projet/magasin désactivé 409 et une panne du backend d'autorisation 500. Les portefeuilles de réception de l'opérateur sont réservés au panneau Opérateur, pas aux identifiants API commerçant ni à MCP, même avec une ancienne autorisation explicite de projet. |
| 403 | project_access_denied | Une revérification transactionnelle à la création a constaté que l'identifiant n'a plus accès au projet. |
| 404 | invoice_not_found | Aucune facture avec cet identifiant public n'existe dans le projet autorisé, ou la page de paiement ne peut pas l'exposer. |
| 404 | payment_resource_not_found | Un projet, magasin, actif ou portefeuille nécessaire à la préparation de la facture n'existe plus. |
| 404 | token_candidate_not_found | Le projet est indisponible ou le token n'est plus présent dans le catalogue de découverte correspondant actuel. |
| 409 | idempotency_conflict | La clé limitée au magasin existe déjà et l'identifiant ou les octets bruts exacts de la requête diffèrent. |
| 409 | store_unavailable | Projet/magasin désactivé ou indisponible. |
| 409 | no_ready_payment_methods | Aucun moyen du magasin n'est prêt. Lis error.message et error.details.payment_methods pour chain_slug, asset_ticker et reason_code. La sauvegarde/activation du portefeuille, l'adaptateur installé et les prix doivent être valides. Depuis 6.0.6, les pauses de scanner, contrôles d'état échoués ou périmés et quorum de fournisseurs manquant ne bloquent pas la création. |
| 409 | payment_method_unavailable | Un moyen sélectionné est devenu indisponible pendant la revérification atomique à la création. |
| 409 | store_payment_method_not_selected | Une dérogation de confirmation du magasin a été demandée pour un actif qui n'est pas actuellement sélectionné par ce magasin. |
| 409 | wallet_unavailable | Un portefeuille de paiement est devenu indisponible pendant la revérification atomique à la création. |
| 409 | ipn_secret_required | Une URL IPN effective existe, mais le magasin n'a pas de secret de signature IPN. |
| 409 | payment_resource_not_ready | Un actif ou portefeuille de paiement requis est désactivé, non sauvegardé, en attente de preuve d'activation du compte partagé, épuisé ou autrement non prêt. |
| 409 | account_activation_unverified | L'activation du compte XRP Ledger ou Stellar n'a pas pu être prouvée auprès du nombre configuré d'endpoints opérationnels du réseau principal (2 par défaut, 1 en option) ; alimente le compte exact et réessaie la vérification. |
| 400 | invalid_monero_wallet_rpc | Endpoint HTTPS, adresse principale exacte du réseau principal, libellé ou données complètes d'authentification Digest/Basic/en-tête invalides. |
| 404 | monero_wallet_rpc_not_found | La liaison Monero wallet-RPC limitée au projet n'existe pas. |
| 409 | monero_wallet_rpc_not_ready | L'actif Monero, le quorum de deux démons, la liaison immuable ou l'attestation explicite de sauvegarde/consultation seule n'est pas prêt. |
| 409 | monero_wallet_rpc_unavailable | La création de factures nécessite une liaison Monero wallet-RPC du projet active, vérifiée et attestée, avec un identifiant côté serveur valide. |
| 503 | lightning_unavailable | Le seul moyen prêt du magasin est Lightning et son portefeuille ou devis n'a pas pu être vérifié. Réessaie avec la même clé d'idempotence. S'il existe un autre moyen on-chain prêt, le moyen Lightning indisponible est simplement omis. |
| 422 | monero_wallet_rpc_verification_failed | Échec de la vérification du portefeuille exact, du verrouillage HTTPS, de la synchronisation, du quorum des démons du réseau principal ou de la preuve de refus de méthode par la passerelle. |
| 503 | monero_wallet_rpc_failed | Le wallet-RPC externe en observation seule n'a pas pu créer et relire en sécurité la sous-adresse de facture ; aucune adresse de secours n'est inventée. |
| 409 | token_chain_not_ready | L'actif natif de la blockchain est désactivé, la correspondance de découverte a changé pendant la vérification ou le projet a déjà le maximum actuel de 20 actifs de tokens enregistrés. |
| 503 | dex_price_unavailable | Fournisseur DEX indisponible, occupé, limité en requêtes, réponse périmée ou données malformées. Réessaie après une minute ; le prix fixe reste disponible. |
| 422 | invalid_dex_price | Combinaison de mode de prix invalide ou le pool sélectionné ne peut pas fournir de prix admissible pour le contrat exact. Choisis un autre pool ou un prix fixe en USD. |
| 422 | token_verification_failed | Tous les nœuds éligibles ont échoué à la vérification de l'identité de blockchain, du code du contrat, des décimales, de la requête de solde ou du mint. |
| 422 | invalid_store_confirmation_policy | La dérogation du magasin est indisponible pour ce mode de finalité, hors des limites propres à la blockchain renvoyées ou demande une acceptation sans confirmation non prise en charge. |
| 409 | invoice_not_payable | La facture de paiement est dans un état terminal ou son échéance de paiement est dépassée. |
| 409 | invoice_payment_method_locked | Un paiement valide a déjà sélectionné un autre actif ; continue avec active_payment_method_id. |
| 409 | payment_method_not_payable | Le moyen sélectionné est terminé ou n'accepte plus d'autre paiement. |
| 422 | payment_qr_unavailable | La demande de paiement est trop grande pour être encodée dans une image QR SVG. |
| 503 | payment_rates_unavailable | Aucun devis récent et fiable n'est disponible pour les moyens de paiement prêts. |
| 500 | authentication_unavailable | L'authentification bearer n'a pas pu lire ou valider en sécurité l'identifiant enregistré. |
| 429 | rate_limit_exceeded | Cet identifiant a épuisé son quota de la minute UTC actuelle. Attends au moins Retry-After secondes ; réessaie la création de facture avec la même clé d'idempotence. |
| 500 | database_error / internal_error | Échec temporaire côté serveur ; réessaie en sécurité avec la même clé d'idempotence. |
Vue d'ensemble de l'API
Choisis un endpoint pour ses champs, exemples et réponse.
Factures
POSTCréer une facture/v1/projects/{project_id}/stores/{store_id}/invoicesGETLister les factures/v1/projects/{project_id}/invoicesGETRécupérer une facture/v1/projects/{project_id}/invoices/{invoice_id}GETLister les paiements d'une facture/v1/projects/{project_id}/invoices/{invoice_id}/paymentsMoyens de paiement
GETLister les actifs de paiement du projet/v1/projects/{project_id}/payment-assetsPUTMettre à jour la politique d'actifs du projet/v1/projects/{project_id}/payment-assets/{asset_id}GETParcourir les tokens candidats aux paiements/v1/projects/{project_id}/payment-token-candidatesPOSTVérifier et enregistrer un token/v1/projects/{project_id}/payment-token-assetsGETTrouver les pools DEX d'un token personnalisé/v1/projects/{project_id}/payment-token-dex-poolsPOSTAjouter ou modifier le prix d'un token personnalisé/v1/projects/{project_id}/payment-token-assets/customGETLister les moyens de paiement du magasin/v1/projects/{project_id}/stores/{store_id}/payment-assetsPUTRemplacer les moyens de paiement du magasin/v1/projects/{project_id}/stores/{store_id}/payment-assetsPUTDéfinir une politique de confirmation du magasin/v1/projects/{project_id}/stores/{store_id}/payment-assets/{asset_id}/confirmation-policyPortefeuilles
GETLister les portefeuilles et soldes du projet/v1/projects/{project_id}/walletsRapprochement
GETLister les exceptions de paiement/v1/projects/{project_id}/reconciliationGETLire les preuves de rapprochement/v1/projects/{project_id}/reconciliation/{invoice_id}API opérateur
GETCapacités/v1/operator/capabilitiesGETÉtat/v1/operator/healthGETLister les commerçants/v1/operator/merchantsPOSTCréer un commerçant/v1/operator/merchantsGETRécupérer un commerçant/v1/operator/merchants/{merchant_id}POSTMettre à jour un commerçant/v1/operator/merchants/{merchant_id}GETLister les utilisateurs/v1/operator/merchants/{merchant_id}/usersPOSTCréer un utilisateur/v1/operator/merchants/{merchant_id}/usersGETRécupérer un utilisateur/v1/operator/merchants/{merchant_id}/users/{user_id}POSTMettre à jour un utilisateur/v1/operator/merchants/{merchant_id}/users/{user_id}POSTDéfinir le mot de passe d'un utilisateur/v1/operator/merchants/{merchant_id}/users/{user_id}/passwordPOSTRévoquer les sessions d'un utilisateur/v1/operator/merchants/{merchant_id}/users/{user_id}/revoke-sessionsGETLister les invitations/v1/operator/merchants/{merchant_id}/invitationsPOSTCréer une invitation/v1/operator/merchants/{merchant_id}/invitationsGETRécupérer une invitation/v1/operator/invitations/{invitation_id}POSTRenvoyer une invitation/v1/operator/invitations/{invitation_id}/resendPOSTRévoquer une invitation/v1/operator/invitations/{invitation_id}/revokeGETRécupérer les crédits/v1/operator/merchants/{merchant_id}/creditsGETLister le registre des crédits/v1/operator/merchants/{merchant_id}/credits/ledgerPOSTAjuster les crédits/v1/operator/merchants/{merchant_id}/credits/adjustmentsGETLister les recharges/v1/operator/merchants/{merchant_id}/topupsPOSTCréer une recharge/v1/operator/merchants/{merchant_id}/topupsGETRécupérer une recharge/v1/operator/merchants/{merchant_id}/topups/{topup_id}GETRapports/v1/operator/reportsGETLister l'audit/v1/operator/auditGETLister les événements/v1/operator/eventsGETLister les webhooks/v1/operator/webhooksPOSTCréer un webhook/v1/operator/webhooksPOSTMettre à jour un webhook/v1/operator/webhooks/{webhook_id}POSTRenouveler le secret d'un webhook/v1/operator/webhooks/{webhook_id}/rotateGETLister les livraisons webhook/v1/operator/webhooks/{webhook_id}/deliveriesGETLister les projets/v1/operator/merchants/{merchant_id}/projectsPOSTCréer un projet/v1/operator/merchants/{merchant_id}/projectsGETRécupérer un projet/v1/operator/merchants/{merchant_id}/projects/{project_id}POSTMettre à jour un projet/v1/operator/merchants/{merchant_id}/projects/{project_id}GETLister les magasins/v1/operator/merchants/{merchant_id}/projects/{project_id}/storesPOSTCréer un magasin/v1/operator/merchants/{merchant_id}/projects/{project_id}/storesGETRécupérer un magasin/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}POSTMettre à jour un magasin/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}GETRécupérer l'apparence du magasin/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/checkout-appearancePOSTMettre à jour l'apparence du magasin/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/checkout-appearanceGETLister les actifs de paiement du magasin/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/payment-assetsPOSTMettre à jour les actifs de paiement du magasin/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/payment-assetsGETLister les webhooks du magasin/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/webhooksPOSTCréer un webhook de magasin/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/webhooksPOSTMettre à jour un webhook de magasin/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/webhooks/{webhook_id}GETLister les factures/v1/operator/merchants/{merchant_id}/projects/{project_id}/invoicesGETRécupérer une facture/v1/operator/merchants/{merchant_id}/projects/{project_id}/invoices/{invoice_id}GETLister les portefeuilles/v1/operator/merchants/{merchant_id}/projects/{project_id}/walletsGETLister les adresses des portefeuilles/v1/operator/merchants/{merchant_id}/projects/{project_id}/wallets/{wallet_id}/addressesGETLister les identifiants commerçant/v1/operator/merchants/{merchant_id}/api-credentialsPOSTCréer un identifiant commerçant/v1/operator/merchants/{merchant_id}/api-credentialsPOSTMettre à jour un identifiant commerçant/v1/operator/merchants/{merchant_id}/api-credentials/{credential_id}POSTRenouveler un identifiant commerçant/v1/operator/merchants/{merchant_id}/api-credentials/{credential_id}/rotatePOSTRévoquer un identifiant commerçant/v1/operator/merchants/{merchant_id}/api-credentials/{credential_id}/revokePOSTVérifier un jeton d'invitation/v1/onboarding/invitations/checkPOSTAccepter une invitation ou une réinitialisation de mot de passe/v1/onboarding/invitations/acceptPage de paiement
GETStructure de la page de paiement/GETPage de paiement hébergée/invoice/{invoice_id}GETFacture avec données adaptées au paiement public/checkout-api/invoices/{invoice_id}GETAperçu de la page de paiement du magasin/invoice/preview/{project_id}GETDonnées d'aperçu du paiement/checkout-api/previews/{project_id}GETImage de paiement du magasin/checkout-api/invoices/{invoice_id}/appearance-images/{kind}/{revision}/image.pngGETImage d'aperçu du magasin/checkout-api/previews/{project_id}/stores/{store_id}/appearance-images/{kind}/{revision}/image.pngGETLogo d'aperçu avec révision/checkout-api/previews/{project_id}/logo/{revision}/image.pngGETImage QR de paiement/checkout-api/invoices/{invoice_id}/payment-methods/{intent_id}/qr.svgGETLogo de paiement avec révision/checkout-api/invoices/{invoice_id}/logo/{revision}/image.pngService
GETDécouverte du service API/GETÉtat du service/healthzGETCapacités/v1/operator/capabilitiesLecture seule
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite health.read ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/capabilities" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/capabilities", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/capabilities");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/capabilities",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"api_version": "v1",
"operator_version": "7.4.0",
"scopes": [
"health.read"
],
"all_merchants": false,
"merchant_ids": [
"11111111-1111-4111-8111-111111111111"
],
"onboarding": [
"direct",
"invitation"
],
"write_methods": [
"POST"
],
"idempotency_required": true
}GETÉtat/v1/operator/healthLecture seule
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite health.read ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/health" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/health", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/health");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/health",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"version": "7.4.0",
"nodes": []
}GETLister les commerçants/v1/operator/merchantsLecture seule
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite merchants.read ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| page, search | query · optional | Pages à partir de 1, 25 éléments par page. La recherche est prise en charge pour les commerçants, utilisateurs, projets, magasins, portefeuilles, identifiants et webhooks ; les listes d'événements natives utilisent leurs filtres propres. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POSTCréer un commerçant/v1/operator/merchantsLecture + écriture
Crée atomiquement un commerçant hébergé et son premier administrateur, directement avec un mot de passe ou sur invitation.
- Nécessite merchants.write ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Enregistre un Idempotency-Key unique et le corps exact avant l'envoi. Les nouvelles tentatives ne répètent jamais une action validée. Les champs secrets sont omis lors d'un rejeu ; si une réponse contenant un secret a été perdue, examine la ressource créée et renouvelle/réémets explicitement le secret. Un 409 operator_request_in_progress peut indiquer une requête interrompue au résultat inconnu : examine la ressource/l'audit ; ne réessaie pas aveuglément avec une nouvelle clé.
- Nécessite l'accès global aux commerçants. Les frais explicitement personnalisés nécessitent aussi fees.write ; starting_credit non nul nécessite credits.write. L'inscription par invitation nécessite aussi invitations.write. Aucune connexion automatique, aucun contournement de Basic Auth, aucun crédit rétroactif lors des nouvelles tentatives.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatoire | 16–128 lettres, chiffres, -, _ ou . ; enregistrée pour cette opération |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| name, email | string · required | Nom du commerçant et e-mail du premier administrateur globalement unique. |
| onboarding | direct | invitation · required | direct nécessite password et n'envoie pas d'e-mail d'invitation. invitation omet password. |
| password | string · direct only | 12–128 caractères (512 octets UTF-8 maximum) ; jamais renvoyé ni envoyé par e-mail. Utilise require_password_change pour les mots de passe temporaires. |
| require_password_change | boolean · default false | Exige un nouveau mot de passe à la première connexion. Chaque compte créé directement doit reconnaître la garde hébergée des portefeuilles. |
| currency | fiat code · optional | Devise du compte prépayé ; utilise la devise régionale par défaut et ne peut plus changer ensuite. |
| fee_bps | integer · optional | 0–10000 ; 100 signifie 1 %. Utilise la valeur par défaut de l'opérateur si omis. Nécessite fees.write. |
| starting_credit | decimal string · default 0 | Crédit local exact accordé une seule fois. Une valeur non nulle nécessite credits.write. Ne recharge pas le solde d'installation de l'opérateur. |
| external_id | string · optional | Référence d'intégration unique, 1–120 caractères. |
| default_timezone | IANA timezone · optional | Utilise par défaut le fuseau horaire régional de l'installation. |
| send_invitation_email | boolean · default false | Invitation uniquement. Nécessite SMTP configuré ; la réponse distingue l'acceptation du relais de la création du compte. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Example shop",
"email": "admin@example.test",
"onboarding": "direct",
"password": "REPLACE_WITH_A_UNIQUE_TEMPORARY_PASSWORD",
"require_password_change": true,
"currency": "EUR",
"starting_credit": "0",
"external_id": "customer-1042"
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"name": "Example shop",
"email": "admin@example.test",
"onboarding": "direct",
"password": "REPLACE_WITH_A_UNIQUE_TEMPORARY_PASSWORD",
"require_password_change": true,
"currency": "EUR",
"starting_credit": "0",
"external_id": "customer-1042"
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"name": "Example shop",
"email": "admin@example.test",
"onboarding": "direct",
"password": "REPLACE_WITH_A_UNIQUE_TEMPORARY_PASSWORD",
"require_password_change": true,
"currency": "EUR",
"starting_credit": "0",
"external_id": "customer-1042"
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"name": "Example shop",
"email": "admin@example.test",
"onboarding": "direct",
"password": "REPLACE_WITH_A_UNIQUE_TEMPORARY_PASSWORD",
"require_password_change": true,
"currency": "EUR",
"starting_credit": "0",
"external_id": "customer-1042"
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 201 ou 200 application/json
{
"merchant_id": "11111111-1111-4111-8111-111111111111",
"user_id": "22222222-2222-4222-8222-222222222222",
"merchant": {
"id": "11111111-1111-4111-8111-111111111111",
"name": "Example shop",
"currency": "EUR",
"balance": "0"
},
"onboarding": "direct",
"access_link": null,
"email_delivery": {
"status": "not_requested"
},
"custody_acceptance_required": true
}GETRécupérer un commerçant/v1/operator/merchants/{merchant_id}Lecture seule
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite merchants.read ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"id": "11111111-1111-4111-8111-111111111111",
"name": "Example shop",
"currency": "EUR",
"balance": "10",
"fee_bps": 300,
"enabled": true,
"payments_paused": false,
"external_id": "customer-1042"
}POSTMettre à jour un commerçant/v1/operator/merchants/{merchant_id}Lecture + écriture
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite merchants.write ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Enregistre un Idempotency-Key unique et le corps exact avant l'envoi. Les nouvelles tentatives ne répètent jamais une action validée. Les champs secrets sont omis lors d'un rejeu ; si une réponse contenant un secret a été perdue, examine la ressource créée et renouvelle/réémets explicitement le secret. Un 409 operator_request_in_progress peut indiquer une requête interrompue au résultat inconnu : examine la ressource/l'audit ; ne réessaie pas aveuglément avec une nouvelle clé.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatoire | 16–128 lettres, chiffres, -, _ ou . ; enregistrée pour cette opération |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| name, enabled, payments_paused, fee_bps, external_id | optional fields | La désactivation révoque les sessions. payments_paused bloque les nouvelles factures, pas la détection des paiements existants. Les modifications de frais nécessitent fees.write et affectent les futures factures ; la devise du compte ne peut pas changer. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"payments_paused": true
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"payments_paused": true
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"payments_paused": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"payments_paused": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"id": "11111111-1111-4111-8111-111111111111",
"name": "Example shop",
"currency": "EUR",
"balance": "10",
"fee_bps": 300,
"enabled": true,
"payments_paused": false
}GETLister les utilisateurs/v1/operator/merchants/{merchant_id}/usersLecture seule
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite users.read ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| page, search | query · optional | Pages à partir de 1, 25 éléments par page. La recherche est prise en charge pour les commerçants, utilisateurs, projets, magasins, portefeuilles, identifiants et webhooks ; les listes d'événements natives utilisent leurs filtres propres. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POSTCréer un utilisateur/v1/operator/merchants/{merchant_id}/usersLecture + écriture
Ajoute un administrateur du commerçant ou un utilisateur limité aux projets sélectionnés.
- Nécessite users.write ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Enregistre un Idempotency-Key unique et le corps exact avant l'envoi. Les nouvelles tentatives ne répètent jamais une action validée. Les champs secrets sont omis lors d'un rejeu ; si une réponse contenant un secret a été perdue, examine la ressource créée et renouvelle/réémets explicitement le secret. Un 409 operator_request_in_progress peut indiquer une requête interrompue au résultat inconnu : examine la ressource/l'audit ; ne réessaie pas aveuglément avec une nouvelle clé.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatoire | 16–128 lettres, chiffres, -, _ ou . ; enregistrée pour cette opération |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| email, display_name | strings · required | L'e-mail est unique dans l'installation. |
| onboarding, password, require_password_change, send_invitation_email | same as merchant creation | La création d'invitations nécessite aussi invitations.write. |
| access_level | admin | projects · default admin | admin est uniquement l'administrateur de ce commerçant, jamais celui de l'installation/opérateur. |
| project_ids | UUID[] | Uniquement les projets du commerçant. Sélections requises pour l'accès limité aux projets ; jamais entre clients distincts. |
| default_timezone | IANA timezone · optional | Valeur régionale par défaut si omis. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"email": "staff@example.test",
"display_name": "Store team",
"onboarding": "invitation",
"access_level": "projects",
"project_ids": [
"11111111-1111-4111-8111-111111111111"
],
"send_invitation_email": false
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"email": "staff@example.test",
"display_name": "Store team",
"onboarding": "invitation",
"access_level": "projects",
"project_ids": [
"11111111-1111-4111-8111-111111111111"
],
"send_invitation_email": false
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"email": "staff@example.test",
"display_name": "Store team",
"onboarding": "invitation",
"access_level": "projects",
"project_ids": [
"11111111-1111-4111-8111-111111111111"
],
"send_invitation_email": false
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"email": "staff@example.test",
"display_name": "Store team",
"onboarding": "invitation",
"access_level": "projects",
"project_ids": [
"11111111-1111-4111-8111-111111111111"
],
"send_invitation_email": false
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 201 ou 200 application/json
{
"user": {
"id": "22222222-2222-4222-8222-222222222222",
"merchant_id": "11111111-1111-4111-8111-111111111111",
"email": "admin@example.test",
"display_name": "Shop administrator",
"access_level": "admin",
"project_ids": [],
"enabled": true,
"password_setup_required": false,
"custody_acceptance_required": true,
"password_change_required": true
},
"user_id": "22222222-2222-4222-8222-222222222222",
"access_link": null,
"email_delivery": {
"status": "not_requested"
},
"merchant_id": "11111111-1111-4111-8111-111111111111"
}GETRécupérer un utilisateur/v1/operator/merchants/{merchant_id}/users/{user_id}Lecture seule
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite users.read ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| user_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"id": "22222222-2222-4222-8222-222222222222",
"merchant_id": "11111111-1111-4111-8111-111111111111",
"email": "admin@example.test",
"display_name": "Shop administrator",
"access_level": "admin",
"project_ids": [],
"enabled": true,
"password_setup_required": false,
"custody_acceptance_required": true,
"password_change_required": true
}POSTMettre à jour un utilisateur/v1/operator/merchants/{merchant_id}/users/{user_id}Lecture + écriture
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite users.write ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Enregistre un Idempotency-Key unique et le corps exact avant l'envoi. Les nouvelles tentatives ne répètent jamais une action validée. Les champs secrets sont omis lors d'un rejeu ; si une réponse contenant un secret a été perdue, examine la ressource créée et renouvelle/réémets explicitement le secret. Un 409 operator_request_in_progress peut indiquer une requête interrompue au résultat inconnu : examine la ressource/l'audit ; ne réessaie pas aveuglément avec une nouvelle clé.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatoire | 16–128 lettres, chiffres, -, _ ou . ; enregistrée pour cette opération |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| user_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| email, display_name, enabled, access_level, project_ids, default_timezone | optional fields | Met à jour les champs fournis ; la protection du dernier administrateur reste. Les mots de passe ont une opération users.security distincte. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"display_name": "Store manager"
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"display_name": "Store manager"
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"display_name": "Store manager"
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"display_name": "Store manager"
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"id": "22222222-2222-4222-8222-222222222222",
"merchant_id": "11111111-1111-4111-8111-111111111111",
"email": "admin@example.test",
"display_name": "Shop administrator",
"access_level": "admin",
"project_ids": [],
"enabled": true,
"password_setup_required": false,
"custody_acceptance_required": true,
"password_change_required": true
}POSTDéfinir le mot de passe d'un utilisateur/v1/operator/merchants/{merchant_id}/users/{user_id}/passwordLecture + écriture
Définit le mot de passe d'un compte hébergé et révoque les sessions. Le TOTP existant est conservé.
- Nécessite users.security ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Enregistre un Idempotency-Key unique et le corps exact avant l'envoi. Les nouvelles tentatives ne répètent jamais une action validée. Les champs secrets sont omis lors d'un rejeu ; si une réponse contenant un secret a été perdue, examine la ressource créée et renouvelle/réémets explicitement le secret. Un 409 operator_request_in_progress peut indiquer une requête interrompue au résultat inconnu : examine la ressource/l'audit ; ne réessaie pas aveuglément avec une nouvelle clé.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatoire | 16–128 lettres, chiffres, -, _ ou . ; enregistrée pour cette opération |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| user_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| password | string · required | Change le mot de passe et révoque les sessions en conservant TOTP. Nécessite users.security. |
| require_password_change | boolean · default true | L'utilisateur doit définir son propre mot de passe à la prochaine connexion réussie. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID/password" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"password": "REPLACE_WITH_A_NEW_UNIQUE_PASSWORD",
"require_password_change": true
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"password": "REPLACE_WITH_A_NEW_UNIQUE_PASSWORD",
"require_password_change": true
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID/password", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"password": "REPLACE_WITH_A_NEW_UNIQUE_PASSWORD",
"require_password_change": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID/password");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"password": "REPLACE_WITH_A_NEW_UNIQUE_PASSWORD",
"require_password_change": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID/password",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"user_id": "11111111-1111-4111-8111-111111111111",
"sessions_revoked": true,
"totp_preserved": true
}POSTRévoquer les sessions d'un utilisateur/v1/operator/merchants/{merchant_id}/users/{user_id}/revoke-sessionsLecture + écriture
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite users.security ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Enregistre un Idempotency-Key unique et le corps exact avant l'envoi. Les nouvelles tentatives ne répètent jamais une action validée. Les champs secrets sont omis lors d'un rejeu ; si une réponse contenant un secret a été perdue, examine la ressource créée et renouvelle/réémets explicitement le secret. Un 409 operator_request_in_progress peut indiquer une requête interrompue au résultat inconnu : examine la ressource/l'audit ; ne réessaie pas aveuglément avec une nouvelle clé.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatoire | 16–128 lettres, chiffres, -, _ ou . ; enregistrée pour cette opération |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| user_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID/revoke-sessions" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID/revoke-sessions", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID/revoke-sessions");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/users/YOUR_USER_ID/revoke-sessions",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"user_id": "11111111-1111-4111-8111-111111111111",
"sessions_revoked": true,
"totp_preserved": true
}GETLister les invitations/v1/operator/merchants/{merchant_id}/invitationsLecture seule
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite invitations.read ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| page, search | query · optional | Pages à partir de 1, 25 éléments par page. La recherche est prise en charge pour les commerçants, utilisateurs, projets, magasins, portefeuilles, identifiants et webhooks ; les listes d'événements natives utilisent leurs filtres propres. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/invitations" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/invitations", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/invitations");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/invitations",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POSTCréer une invitation/v1/operator/merchants/{merchant_id}/invitationsLecture + écriture
Crée ou remplace un lien d'invitation ou de réinitialisation de mot de passe à usage unique.
- Nécessite invitations.write ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Enregistre un Idempotency-Key unique et le corps exact avant l'envoi. Les nouvelles tentatives ne répètent jamais une action validée. Les champs secrets sont omis lors d'un rejeu ; si une réponse contenant un secret a été perdue, examine la ressource créée et renouvelle/réémets explicitement le secret. Un 409 operator_request_in_progress peut indiquer une requête interrompue au résultat inconnu : examine la ressource/l'audit ; ne réessaie pas aveuglément avec une nouvelle clé.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatoire | 16–128 lettres, chiffres, -, _ ou . ; enregistrée pour cette opération |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| user_id, send_email | UUID, boolean | Émet/remplace un lien à usage unique pour un compte existant. Les utilisateurs activés reçoivent un lien de réinitialisation d'une heure et nécessitent users.security. |
| new user fields | alternative to user_id | Utilise email, display_name, access_level et project_ids pour créer un utilisateur invité ; nécessite users.write. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/invitations" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"user_id": "11111111-1111-4111-8111-111111111111",
"send_email": false
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"user_id": "11111111-1111-4111-8111-111111111111",
"send_email": false
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/invitations", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"user_id": "11111111-1111-4111-8111-111111111111",
"send_email": false
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/invitations");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"user_id": "11111111-1111-4111-8111-111111111111",
"send_email": false
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/invitations",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 201 ou 200 application/json
{
"user_id": "22222222-2222-4222-8222-222222222222",
"email": "admin@example.test",
"kind": "invitation",
"expires_at": "2026-10-03T12:00:00Z",
"url": "https://merchant.example.com/account-access.html#token=YOUR_PRIVATE_INVITATION_TOKEN"
}GETRécupérer une invitation/v1/operator/invitations/{invitation_id}Lecture seule
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite invitations.read ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| invitation_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"id": "44444444-4444-4444-8444-444444444444",
"user_id": "22222222-2222-4222-8222-222222222222",
"merchant_id": "11111111-1111-4111-8111-111111111111",
"kind": "invitation",
"status": "pending",
"created_at": "2026-10-01T12:00:00Z",
"expires_at": "2026-10-03T12:00:00Z"
}POSTRenvoyer une invitation/v1/operator/invitations/{invitation_id}/resendLecture + écriture
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite invitations.write ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Enregistre un Idempotency-Key unique et le corps exact avant l'envoi. Les nouvelles tentatives ne répètent jamais une action validée. Les champs secrets sont omis lors d'un rejeu ; si une réponse contenant un secret a été perdue, examine la ressource créée et renouvelle/réémets explicitement le secret. Un 409 operator_request_in_progress peut indiquer une requête interrompue au résultat inconnu : examine la ressource/l'audit ; ne réessaie pas aveuglément avec une nouvelle clé.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatoire | 16–128 lettres, chiffres, -, _ ou . ; enregistrée pour cette opération |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| invitation_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| send_email | boolean · default false | Remplace le jeton précédent, n'ajoute jamais de crédit. Renvoie une seule fois un lien nouvellement généré. Un compte déjà activé nécessite users.security. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID/resend" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"send_email": false
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"send_email": false
}`;
const response = await fetch("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID/resend", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"send_email": false
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID/resend");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"send_email": false
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID/resend",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"user_id": "22222222-2222-4222-8222-222222222222",
"email": "admin@example.test",
"kind": "invitation",
"expires_at": "2026-10-03T12:00:00Z",
"url": "https://merchant.example.com/account-access.html#token=YOUR_PRIVATE_INVITATION_TOKEN"
}POSTRévoquer une invitation/v1/operator/invitations/{invitation_id}/revokeLecture + écriture
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite invitations.write ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Enregistre un Idempotency-Key unique et le corps exact avant l'envoi. Les nouvelles tentatives ne répètent jamais une action validée. Les champs secrets sont omis lors d'un rejeu ; si une réponse contenant un secret a été perdue, examine la ressource créée et renouvelle/réémets explicitement le secret. Un 409 operator_request_in_progress peut indiquer une requête interrompue au résultat inconnu : examine la ressource/l'audit ; ne réessaie pas aveuglément avec une nouvelle clé.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatoire | 16–128 lettres, chiffres, -, _ ou . ; enregistrée pour cette opération |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| invitation_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID/revoke" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{}`;
const response = await fetch("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID/revoke", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID/revoke");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/invitations/YOUR_INVITATION_ID/revoke",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"revoked": true,
"invitation_id": "44444444-4444-4444-8444-444444444444"
}GETRécupérer les crédits/v1/operator/merchants/{merchant_id}/creditsLecture seule
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite credits.read ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"id": "11111111-1111-4111-8111-111111111111",
"name": "Example shop",
"currency": "EUR",
"balance": "10",
"fee_bps": 300,
"enabled": true,
"payments_paused": false,
"external_id": "customer-1042"
}GETLister le registre des crédits/v1/operator/merchants/{merchant_id}/credits/ledgerLecture seule
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite credits.read ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| page, q | query · optional | Pages à partir de 1, 25 éléments par page. La recherche est prise en charge pour les commerçants, utilisateurs, projets, magasins, portefeuilles, identifiants et webhooks ; les listes d'événements natives utilisent leurs filtres propres. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits/ledger" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits/ledger", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits/ledger");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits/ledger",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POSTAjuster les crédits/v1/operator/merchants/{merchant_id}/credits/adjustmentsLecture + écriture
Ajoute un crédit accordé ou une correction motivée au registre prépayé de ce commerçant.
- Nécessite credits.write ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Enregistre un Idempotency-Key unique et le corps exact avant l'envoi. Les nouvelles tentatives ne répètent jamais une action validée. Les champs secrets sont omis lors d'un rejeu ; si une réponse contenant un secret a été perdue, examine la ressource créée et renouvelle/réémets explicitement le secret. Un 409 operator_request_in_progress peut indiquer une requête interrompue au résultat inconnu : examine la ressource/l'audit ; ne réessaie pas aveuglément avec une nouvelle clé.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatoire | 16–128 lettres, chiffres, -, _ ou . ; enregistrée pour cette opération |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| amount | signed decimal string · required | Crédit positif ou correction négative, jusqu'à six décimales dans la devise de crédit du commerçant. Ce n'est pas un transfert on-chain. |
| note | string · required | Raison conservée dans le registre à ajout uniquement. |
| request_id | UUID · required | Enregistre avec le montant et la raison, en plus de l'Idempotency-Key HTTP. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits/adjustments" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"amount": "10.00",
"note": "Promotional credit",
"request_id": "11111111-1111-4111-8111-111111111111"
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"amount": "10.00",
"note": "Promotional credit",
"request_id": "11111111-1111-4111-8111-111111111111"
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits/adjustments", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"amount": "10.00",
"note": "Promotional credit",
"request_id": "11111111-1111-4111-8111-111111111111"
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits/adjustments");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"amount": "10.00",
"note": "Promotional credit",
"request_id": "11111111-1111-4111-8111-111111111111"
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/credits/adjustments",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"balance": "25"
}GETLister les recharges/v1/operator/merchants/{merchant_id}/topupsLecture seule
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite topups.read ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| page, search | query · optional | Pages à partir de 1, 25 éléments par page. La recherche est prise en charge pour les commerçants, utilisateurs, projets, magasins, portefeuilles, identifiants et webhooks ; les listes d'événements natives utilisent leurs filtres propres. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POSTCréer une recharge/v1/operator/merchants/{merchant_id}/topupsLecture + écriture
Crée une page de paiement pour du crédit prépayé ; ne la marque jamais manuellement payée.
- Nécessite topups.write ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Enregistre un Idempotency-Key unique et le corps exact avant l'envoi. Les nouvelles tentatives ne répètent jamais une action validée. Les champs secrets sont omis lors d'un rejeu ; si une réponse contenant un secret a été perdue, examine la ressource créée et renouvelle/réémets explicitement le secret. Un 409 operator_request_in_progress peut indiquer une requête interrompue au résultat inconnu : examine la ressource/l'audit ; ne réessaie pas aveuglément avec une nouvelle clé.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatoire | 16–128 lettres, chiffres, -, _ ou . ; enregistrée pour cette opération |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| amount | decimal string · required | Au moins une unité de devise de crédit du commerçant. Nécessite un magasin de réception Opérateur prêt. |
| request_id | UUID · required | Conserve entre les nouvelles tentatives. Renvoie la facture existante si elle est déjà créée. Le crédit n'est appliqué qu'après le règlement observé. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"amount": "25.00",
"request_id": "11111111-1111-4111-8111-111111111111"
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"amount": "25.00",
"request_id": "11111111-1111-4111-8111-111111111111"
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"amount": "25.00",
"request_id": "11111111-1111-4111-8111-111111111111"
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"amount": "25.00",
"request_id": "11111111-1111-4111-8111-111111111111"
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 201 ou 200 application/json
{
"id": "44444444-4444-4444-8444-444444444444",
"invoice_id": "33333333-3333-4333-8333-333333333333",
"checkout_url": "https://pay.example.com/invoice/33333333-3333-4333-8333-333333333333"
}GETRécupérer une recharge/v1/operator/merchants/{merchant_id}/topups/{topup_id}Lecture seule
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite topups.read ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| topup_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups/YOUR_TOPUP_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups/YOUR_TOPUP_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups/YOUR_TOPUP_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/topups/YOUR_TOPUP_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"id": "44444444-4444-4444-8444-444444444444",
"merchant_id": "11111111-1111-4111-8111-111111111111",
"invoice_id": "33333333-3333-4333-8333-333333333333",
"amount": "25",
"currency": "EUR",
"status": "pending",
"invoice_status": "new",
"checkout_url": "https://pay.example.com/invoice/33333333-3333-4333-8333-333333333333"
}GETRapports/v1/operator/reportsLecture seule
Consulte la vue financière de l'Opérateur. Nécessite l'accès à tous les commerçants hébergés.
- Nécessite reports.read ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| period, start, end, currency, timezone, merchant_id | query · optional | Filtres financiers. period vaut last30 par défaut ; utilise custom avec start/end au format YYYY-MM-DD. Identifiants couvrant tous les commerçants uniquement. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/reports" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/reports", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/reports");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/reports",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"summary": {
"fees": "10",
"costs": "3",
"margin": "7",
"credits": "25",
"pending": 0,
"missing_rates": 0
},
"merchants": [],
"filters": {
"period": "last30",
"currency": "EUR",
"timezone": "UTC"
},
"basis": "first_settlement_latest_net_fees"
}GETLister l'audit/v1/operator/auditLecture seule
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite audit.read ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id, event_type / search | query · optional | Filtre le commerçant autorisé, le type exact d'événement (events) ou le texte d'action (audit). Événements conservés : 30 jours. |
| page, search | query · optional | Pages à partir de 1, 25 éléments par page. La recherche est prise en charge pour les commerçants, utilisateurs, projets, magasins, portefeuilles, identifiants et webhooks ; les listes d'événements natives utilisent leurs filtres propres. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/audit" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/audit", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/audit");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/audit",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}GETLister les événements/v1/operator/eventsLecture seule
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite events.read ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id, event_type / search | query · optional | Filtre le commerçant autorisé, le type exact d'événement (events) ou le texte d'action (audit). Événements conservés : 30 jours. |
| page, search | query · optional | Pages à partir de 1, 25 éléments par page. La recherche est prise en charge pour les commerçants, utilisateurs, projets, magasins, portefeuilles, identifiants et webhooks ; les listes d'événements natives utilisent leurs filtres propres. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/events" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/events", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/events");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/events",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}GETLister les webhooks/v1/operator/webhooksLecture seule
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite events.read ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| page, search | query · optional | Pages à partir de 1, 25 éléments par page. La recherche est prise en charge pour les commerçants, utilisateurs, projets, magasins, portefeuilles, identifiants et webhooks ; les listes d'événements natives utilisent leurs filtres propres. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/webhooks" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/webhooks", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/webhooks");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/webhooks",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POSTCréer un webhook/v1/operator/webhooksLecture + écriture
Abonne-toi aux futurs événements de cycle de vie Opérateur. Ce n'est pas un webhook de paiement de magasin.
- Nécessite webhooks.write + events.read ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Enregistre un Idempotency-Key unique et le corps exact avant l'envoi. Les nouvelles tentatives ne répètent jamais une action validée. Les champs secrets sont omis lors d'un rejeu ; si une réponse contenant un secret a été perdue, examine la ressource créée et renouvelle/réémets explicitement le secret. Un 409 operator_request_in_progress peut indiquer une requête interrompue au résultat inconnu : examine la ressource/l'audit ; ne réessaie pas aveuglément avec une nouvelle clé.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatoire | 16–128 lettres, chiffres, -, _ ou . ; enregistrée pour cette opération |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| url | public HTTPS URL · required | Aucun identifiant, IP privée ni redirection. DNS/IP revérifiés à la livraison. |
| events | string[] · required | Choisis les événements de cycle de vie dans le guide Opérateur, pas les callbacks de facture. |
| merchant_ids | UUID[] · optional | Vide signifie tous les commerçants autorisés par cet identifiant. Les restrictions du périmètre actuel sont revérifiées. |
| enabled | boolean · default true | Les endpoints suspendus conservent les livraisons en file ; leur réactivation reprend le travail conservé encore valide. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/webhooks" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://shop.example.test/operator-events",
"events": [
"merchant.created",
"topup.settled"
],
"merchant_ids": [],
"enabled": true
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"url": "https://shop.example.test/operator-events",
"events": [
"merchant.created",
"topup.settled"
],
"merchant_ids": [],
"enabled": true
}`;
const response = await fetch("https://api.example.com/v1/operator/webhooks", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"url": "https://shop.example.test/operator-events",
"events": [
"merchant.created",
"topup.settled"
],
"merchant_ids": [],
"enabled": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/webhooks");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"url": "https://shop.example.test/operator-events",
"events": [
"merchant.created",
"topup.settled"
],
"merchant_ids": [],
"enabled": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/webhooks",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 201 ou 200 application/json
{
"id": "11111111-1111-4111-8111-111111111111",
"signing_secret": "wco_whsec_EXAMPLE_ONLY_SAVE_ONCE"
}POSTMettre à jour un webhook/v1/operator/webhooks/{webhook_id}Lecture + écriture
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite webhooks.write + events.read ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Enregistre un Idempotency-Key unique et le corps exact avant l'envoi. Les nouvelles tentatives ne répètent jamais une action validée. Les champs secrets sont omis lors d'un rejeu ; si une réponse contenant un secret a été perdue, examine la ressource créée et renouvelle/réémets explicitement le secret. Un 409 operator_request_in_progress peut indiquer une requête interrompue au résultat inconnu : examine la ressource/l'audit ; ne réessaie pas aveuglément avec une nouvelle clé.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatoire | 16–128 lettres, chiffres, -, _ ou . ; enregistrée pour cette opération |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| webhook_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| url | public HTTPS URL · required | Aucun identifiant, IP privée ni redirection. DNS/IP revérifiés à la livraison. |
| events | string[] · required | Choisis les événements de cycle de vie dans le guide Opérateur, pas les callbacks de facture. |
| merchant_ids | UUID[] · optional | Vide signifie tous les commerçants autorisés par cet identifiant. Les restrictions du périmètre actuel sont revérifiées. |
| enabled | boolean · default true | Les endpoints suspendus conservent les livraisons en file ; leur réactivation reprend le travail conservé encore valide. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"url": "https://shop.example.test/operator-events",
"events": [
"topup.settled"
],
"merchant_ids": [],
"enabled": false
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"url": "https://shop.example.test/operator-events",
"events": [
"topup.settled"
],
"merchant_ids": [],
"enabled": false
}`;
const response = await fetch("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"url": "https://shop.example.test/operator-events",
"events": [
"topup.settled"
],
"merchant_ids": [],
"enabled": false
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"url": "https://shop.example.test/operator-events",
"events": [
"topup.settled"
],
"merchant_ids": [],
"enabled": false
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"id": "44444444-4444-4444-8444-444444444444",
"signing_secret": null
}POSTRenouveler le secret d'un webhook/v1/operator/webhooks/{webhook_id}/rotateLecture + écriture
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite webhooks.write + events.read ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Enregistre un Idempotency-Key unique et le corps exact avant l'envoi. Les nouvelles tentatives ne répètent jamais une action validée. Les champs secrets sont omis lors d'un rejeu ; si une réponse contenant un secret a été perdue, examine la ressource créée et renouvelle/réémets explicitement le secret. Un 409 operator_request_in_progress peut indiquer une requête interrompue au résultat inconnu : examine la ressource/l'audit ; ne réessaie pas aveuglément avec une nouvelle clé.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatoire | 16–128 lettres, chiffres, -, _ ou . ; enregistrée pour cette opération |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| webhook_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID/rotate" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{}`;
const response = await fetch("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID/rotate", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID/rotate");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID/rotate",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"id": "11111111-1111-4111-8111-111111111111",
"signing_secret": "wco_whsec_EXAMPLE_ONLY_SAVE_ONCE"
}GETLister les livraisons webhook/v1/operator/webhooks/{webhook_id}/deliveriesLecture seule
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite events.read ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| webhook_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| page | query · optional | Pages à partir de 1, 25 éléments par page. La recherche est prise en charge pour les commerçants, utilisateurs, projets, magasins, portefeuilles, identifiants et webhooks ; les listes d'événements natives utilisent leurs filtres propres. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID/deliveries" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID/deliveries", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID/deliveries");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/webhooks/YOUR_WEBHOOK_ID/deliveries",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}GETLister les projets/v1/operator/merchants/{merchant_id}/projectsLecture seule
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite projects.read ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| page, search | query · optional | Pages à partir de 1, 25 éléments par page. La recherche est prise en charge pour les commerçants, utilisateurs, projets, magasins, portefeuilles, identifiants et webhooks ; les listes d'événements natives utilisent leurs filtres propres. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POSTCréer un projet/v1/operator/merchants/{merchant_id}/projectsLecture + écriture
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite projects.write ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Enregistre un Idempotency-Key unique et le corps exact avant l'envoi. Les nouvelles tentatives ne répètent jamais une action validée. Les champs secrets sont omis lors d'un rejeu ; si une réponse contenant un secret a été perdue, examine la ressource créée et renouvelle/réémets explicitement le secret. Un 409 operator_request_in_progress peut indiquer une requête interrompue au résultat inconnu : examine la ressource/l'audit ; ne réessaie pas aveuglément avec une nouvelle clé.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatoire | 16–128 lettres, chiffres, -, _ ou . ; enregistrée pour cette opération |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| name, slug | strings · required | Nom et identifiant stable unique du projet. Crée des portefeuilles locaux avec l'initialisation existante du projet, ne transfère jamais de fonds. |
| enabled, reporting_timezone, checkout_title, checkout_description, checkout_theme, checkout_accent_color | optional | enabled vaut true par défaut ; il est conseillé de créer en pause et de configurer d'abord un magasin. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Online shop",
"slug": "online-shop",
"enabled": false
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"name": "Online shop",
"slug": "online-shop",
"enabled": false
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"name": "Online shop",
"slug": "online-shop",
"enabled": false
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"name": "Online shop",
"slug": "online-shop",
"enabled": false
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 201 ou 200 application/json
{
"id": "33333333-3333-4333-8333-333333333333",
"slug": "example-shop",
"name": "Example shop",
"enabled": false,
"reporting_currency": "EUR",
"reporting_timezone": "Europe/Berlin",
"stores": []
}GETRécupérer un projet/v1/operator/merchants/{merchant_id}/projects/{project_id}Lecture seule
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite projects.read ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| project_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"id": "33333333-3333-4333-8333-333333333333",
"slug": "example-shop",
"name": "Example shop",
"enabled": false,
"reporting_currency": "EUR",
"reporting_timezone": "Europe/Berlin",
"stores": []
}POSTMettre à jour un projet/v1/operator/merchants/{merchant_id}/projects/{project_id}Lecture + écriture
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite projects.write ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Enregistre un Idempotency-Key unique et le corps exact avant l'envoi. Les nouvelles tentatives ne répètent jamais une action validée. Les champs secrets sont omis lors d'un rejeu ; si une réponse contenant un secret a été perdue, examine la ressource créée et renouvelle/réémets explicitement le secret. Un 409 operator_request_in_progress peut indiquer une requête interrompue au résultat inconnu : examine la ressource/l'audit ; ne réessaie pas aveuglément avec une nouvelle clé.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatoire | 16–128 lettres, chiffres, -, _ ou . ; enregistrée pour cette opération |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| project_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| name, enabled, reporting_currency, reporting_timezone, checkout_title, checkout_description, checkout_theme, checkout_accent_color | optional | Mise à jour partielle. L'identifiant et le commerçant propriétaire ne peuvent pas changer. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"enabled": true
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"enabled": true
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"enabled": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"enabled": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"id": "33333333-3333-4333-8333-333333333333",
"slug": "example-shop",
"name": "Example shop",
"enabled": false,
"reporting_currency": "EUR",
"reporting_timezone": "Europe/Berlin",
"stores": []
}GETLister les magasins/v1/operator/merchants/{merchant_id}/projects/{project_id}/storesLecture seule
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite projects.read ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| project_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| page, search | query · optional | Pages à partir de 1, 25 éléments par page. La recherche est prise en charge pour les commerçants, utilisateurs, projets, magasins, portefeuilles, identifiants et webhooks ; les listes d'événements natives utilisent leurs filtres propres. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POSTCréer un magasin/v1/operator/merchants/{merchant_id}/projects/{project_id}/storesLecture + écriture
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite projects.write ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Enregistre un Idempotency-Key unique et le corps exact avant l'envoi. Les nouvelles tentatives ne répètent jamais une action validée. Les champs secrets sont omis lors d'un rejeu ; si une réponse contenant un secret a été perdue, examine la ressource créée et renouvelle/réémets explicitement le secret. Un 409 operator_request_in_progress peut indiquer une requête interrompue au résultat inconnu : examine la ressource/l'audit ; ne réessaie pas aveuglément avec une nouvelle clé.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatoire | 16–128 lettres, chiffres, -, _ ou . ; enregistrée pour cette opération |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| project_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| name, slug | strings · required | Nom du magasin et identifiant stable. |
| default_currency, invoice_expiry_minutes, exchange_rate_spread_percent, underpayment_tolerance_percent | optional | Utilise des chaînes décimales pour les pourcentages. Les nouveaux magasins héritent de l'apparence du magasin par défaut du projet. |
| enabled, is_default, allow_zero_amount_invoices, allow_underpayments, allow_overpayments, allowed_chain_slugs | optional | Configure les actifs acceptés avec payment-assets ; les factures de montant nul sont désactivées par défaut. |
| ipn_enabled, default_ipn_url, default_redirect_url, default_cancel_url, redirect_automatically | optional | Les IPN et URL de retour suivent la validation d'URL existante. Aucun HTML/JavaScript arbitraire. |
| checkout_language, embed_enabled, allowed_embed_origins, domains | optional | Utilise une langue prise en charge et des domaines actifs pour les rôles concernés ; configure explicitement les origines d'intégration. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Web checkout",
"slug": "web-checkout",
"default_currency": "EUR",
"enabled": false
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"name": "Web checkout",
"slug": "web-checkout",
"default_currency": "EUR",
"enabled": false
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"name": "Web checkout",
"slug": "web-checkout",
"default_currency": "EUR",
"enabled": false
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"name": "Web checkout",
"slug": "web-checkout",
"default_currency": "EUR",
"enabled": false
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 201 ou 200 application/json
{
"id": "44444444-4444-4444-8444-444444444444",
"project_id": "33333333-3333-4333-8333-333333333333",
"slug": "online",
"name": "Online shop",
"enabled": false,
"default_currency": "EUR",
"invoice_expiry_minutes": 60
}GETRécupérer un magasin/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}Lecture seule
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite projects.read ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| project_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| store_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"id": "44444444-4444-4444-8444-444444444444",
"project_id": "33333333-3333-4333-8333-333333333333",
"slug": "online",
"name": "Online shop",
"enabled": false,
"default_currency": "EUR",
"invoice_expiry_minutes": 60
}POSTMettre à jour un magasin/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}Lecture + écriture
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite projects.write ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Enregistre un Idempotency-Key unique et le corps exact avant l'envoi. Les nouvelles tentatives ne répètent jamais une action validée. Les champs secrets sont omis lors d'un rejeu ; si une réponse contenant un secret a été perdue, examine la ressource créée et renouvelle/réémets explicitement le secret. Un 409 operator_request_in_progress peut indiquer une requête interrompue au résultat inconnu : examine la ressource/l'audit ; ne réessaie pas aveuglément avec une nouvelle clé.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatoire | 16–128 lettres, chiffres, -, _ ou . ; enregistrée pour cette opération |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| project_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| store_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| store fields | optional | Mêmes réglages modifiables qu'à la création du magasin, sauf slug. Seuls les champs fournis changent. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"enabled": true
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"enabled": true
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"enabled": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"enabled": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"id": "44444444-4444-4444-8444-444444444444",
"project_id": "33333333-3333-4333-8333-333333333333",
"slug": "online",
"name": "Online shop",
"enabled": false,
"default_currency": "EUR",
"invoice_expiry_minutes": 60
}GETRécupérer l'apparence du magasin/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/checkout-appearanceLecture seule
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite projects.read ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| project_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| store_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/checkout-appearance" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/checkout-appearance", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/checkout-appearance");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/checkout-appearance",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"revision": 1,
"settings": {
"inherit_default_store": true
},
"effective": {
"title": "Pay securely"
}
}POSTMettre à jour l'apparence du magasin/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/checkout-appearanceLecture + écriture
Enregistre un design de magasin validé et protégé par révision.
- Nécessite projects.write ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Enregistre un Idempotency-Key unique et le corps exact avant l'envoi. Les nouvelles tentatives ne répètent jamais une action validée. Les champs secrets sont omis lors d'un rejeu ; si une réponse contenant un secret a été perdue, examine la ressource créée et renouvelle/réémets explicitement le secret. Un 409 operator_request_in_progress peut indiquer une requête interrompue au résultat inconnu : examine la ressource/l'audit ; ne réessaie pas aveuglément avec une nouvelle clé.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatoire | 16–128 lettres, chiffres, -, _ ou . ; enregistrée pour cette opération |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| project_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| store_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| revision | integer · required | Lis d'abord la révision actuelle avec GET. Une révision périmée échoue sans écraser le travail d'un autre éditeur. |
| settings | appearance object · required | Apparence de paiement validée, y compris inherit_default_store, marque, intro/outro, tailles de police et visibilité. Aucun HTML/JavaScript arbitraire. Le téléversement des octets d'image est réservé à la console. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/checkout-appearance" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"revision": 1,
"settings": {
"inherit_default_store": false,
"title": "Pay securely",
"theme": "light"
}
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"revision": 1,
"settings": {
"inherit_default_store": false,
"title": "Pay securely",
"theme": "light"
}
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/checkout-appearance", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"revision": 1,
"settings": {
"inherit_default_store": false,
"title": "Pay securely",
"theme": "light"
}
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/checkout-appearance");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"revision": 1,
"settings": {
"inherit_default_store": false,
"title": "Pay securely",
"theme": "light"
}
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/checkout-appearance",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"settings": {
"inherit_default_store": false,
"title": "Pay securely",
"theme": "light"
},
"revision": 2
}GETLister les actifs de paiement du magasin/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/payment-assetsLecture seule
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite projects.read ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| project_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| store_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"data": []
}POSTMettre à jour les actifs de paiement du magasin/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/payment-assetsLecture + écriture
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite projects.write ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Enregistre un Idempotency-Key unique et le corps exact avant l'envoi. Les nouvelles tentatives ne répètent jamais une action validée. Les champs secrets sont omis lors d'un rejeu ; si une réponse contenant un secret a été perdue, examine la ressource créée et renouvelle/réémets explicitement le secret. Un 409 operator_request_in_progress peut indiquer une requête interrompue au résultat inconnu : examine la ressource/l'audit ; ne réessaie pas aveuglément avec une nouvelle clé.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatoire | 16–128 lettres, chiffres, -, _ ou . ; enregistrée pour cette opération |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| project_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| store_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| assets | array · required | Remplacement complet des moyens on-chain : UUID asset_id et display_order. [] efface les actifs on-chain acceptés. Actifs de projet vérifiés uniquement ; ne configure pas Lightning. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"assets": [
{
"asset_id": "11111111-1111-4111-8111-111111111111",
"display_order": 0
}
]
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"assets": [
{
"asset_id": "11111111-1111-4111-8111-111111111111",
"display_order": 0
}
]
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"assets": [
{
"asset_id": "11111111-1111-4111-8111-111111111111",
"display_order": 0
}
]
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"assets": [
{
"asset_id": "11111111-1111-4111-8111-111111111111",
"display_order": 0
}
]
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"data": []
}GETLister les webhooks du magasin/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/webhooksLecture seule
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite projects.read ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| project_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| store_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| page, search | query · optional | Pages à partir de 1, 25 éléments par page. La recherche est prise en charge pour les commerçants, utilisateurs, projets, magasins, portefeuilles, identifiants et webhooks ; les listes d'événements natives utilisent leurs filtres propres. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POSTCréer un webhook de magasin/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/webhooksLecture + écriture
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite projects.write ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Enregistre un Idempotency-Key unique et le corps exact avant l'envoi. Les nouvelles tentatives ne répètent jamais une action validée. Les champs secrets sont omis lors d'un rejeu ; si une réponse contenant un secret a été perdue, examine la ressource créée et renouvelle/réémets explicitement le secret. Un 409 operator_request_in_progress peut indiquer une requête interrompue au résultat inconnu : examine la ressource/l'audit ; ne réessaie pas aveuglément avec une nouvelle clé.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatoire | 16–128 lettres, chiffres, -, _ ou . ; enregistrée pour cette opération |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| project_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| store_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| name, url, event_types | strings / array · required | Récepteur HTTPS public et noms d'événements de facture de la documentation IPN et webhooks. |
| enabled, automatic_redelivery | booleans · default true | La création renvoie le secret de signature une seule fois. Ce sont des callbacks de paiement du magasin, pas des événements de cycle de vie Opérateur. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Orders",
"url": "https://shop.example.test/payments",
"event_types": [
"invoice.settled"
],
"enabled": true,
"automatic_redelivery": true
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"name": "Orders",
"url": "https://shop.example.test/payments",
"event_types": [
"invoice.settled"
],
"enabled": true,
"automatic_redelivery": true
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"name": "Orders",
"url": "https://shop.example.test/payments",
"event_types": [
"invoice.settled"
],
"enabled": true,
"automatic_redelivery": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"name": "Orders",
"url": "https://shop.example.test/payments",
"event_types": [
"invoice.settled"
],
"enabled": true,
"automatic_redelivery": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 201 ou 200 application/json
{
"signing_secret": "EXAMPLE_ONLY_SAVE_ONCE",
"endpoint": {
"id": "44444444-4444-4444-8444-444444444444",
"name": "Orders",
"enabled": true
},
"secret_visible_once": true
}POSTMettre à jour un webhook de magasin/v1/operator/merchants/{merchant_id}/projects/{project_id}/stores/{store_id}/webhooks/{webhook_id}Lecture + écriture
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite projects.write ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Enregistre un Idempotency-Key unique et le corps exact avant l'envoi. Les nouvelles tentatives ne répètent jamais une action validée. Les champs secrets sont omis lors d'un rejeu ; si une réponse contenant un secret a été perdue, examine la ressource créée et renouvelle/réémets explicitement le secret. Un 409 operator_request_in_progress peut indiquer une requête interrompue au résultat inconnu : examine la ressource/l'audit ; ne réessaie pas aveuglément avec une nouvelle clé.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatoire | 16–128 lettres, chiffres, -, _ ou . ; enregistrée pour cette opération |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| project_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| store_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| webhook_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| name, url, event_types | strings / array · required | Récepteur HTTPS public et noms d'événements de facture de la documentation IPN et webhooks. |
| enabled, automatic_redelivery | booleans · default true | La création renvoie le secret de signature une seule fois. Ce sont des callbacks de paiement du magasin, pas des événements de cycle de vie Opérateur. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks/YOUR_WEBHOOK_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Orders",
"url": "https://shop.example.test/payments",
"event_types": [
"invoice.settled"
],
"enabled": false,
"automatic_redelivery": true
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"name": "Orders",
"url": "https://shop.example.test/payments",
"event_types": [
"invoice.settled"
],
"enabled": false,
"automatic_redelivery": true
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks/YOUR_WEBHOOK_ID", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"name": "Orders",
"url": "https://shop.example.test/payments",
"event_types": [
"invoice.settled"
],
"enabled": false,
"automatic_redelivery": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks/YOUR_WEBHOOK_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"name": "Orders",
"url": "https://shop.example.test/payments",
"event_types": [
"invoice.settled"
],
"enabled": false,
"automatic_redelivery": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/webhooks/YOUR_WEBHOOK_ID",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"id": "44444444-4444-4444-8444-444444444444",
"name": "Orders",
"enabled": true
}GETLister les factures/v1/operator/merchants/{merchant_id}/projects/{project_id}/invoicesLecture seule
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite reports.read ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| project_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| limit, offset, search, status, store_id | query · optional | Pagination et filtres de factures, comme dans la liste de factures du projet. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/invoices" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/invoices", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/invoices");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/invoices",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"data": [],
"pagination": {
"limit": 25,
"offset": 0,
"total": 0,
"has_more": false
}
}GETRécupérer une facture/v1/operator/merchants/{merchant_id}/projects/{project_id}/invoices/{invoice_id}Lecture seule
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite reports.read ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
- invoice_id est l'identifiant public de facture renvoyé à la création et dans les callbacks, pas l'id interne.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| project_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| invoice_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/invoices/YOUR_INVOICE_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/invoices/YOUR_INVOICE_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/invoices/YOUR_INVOICE_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/invoices/YOUR_INVOICE_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"invoice_id": "44444444-4444-4444-8444-444444444444",
"project_id": "33333333-3333-4333-8333-333333333333",
"amount": "25",
"currency": "EUR",
"status": "new",
"metadata": {},
"payment_intents": []
}GETLister les portefeuilles/v1/operator/merchants/{merchant_id}/projects/{project_id}/walletsLecture seule
Lis les soldes publics de portefeuilles en cache, jamais les clés privées ni phrases de récupération.
- Nécessite reports.read ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Les soldes sont des observations en cache avec des champs de fraîcheur, pas une garantie de solde dépensable. L'envoi et l'export des clés ne sont pas disponibles via cette API.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| project_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| page, search | query · optional | Pages à partir de 1, 25 éléments par page. La recherche est prise en charge pour les commerçants, utilisateurs, projets, magasins, portefeuilles, identifiants et webhooks ; les listes d'événements natives utilisent leurs filtres propres. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/wallets" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/wallets", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/wallets");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/wallets",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}GETLister les adresses des portefeuilles/v1/operator/merchants/{merchant_id}/projects/{project_id}/wallets/{wallet_id}/addressesLecture seule
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite reports.read ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| project_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| wallet_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| limit, before, search, has_balance, hide_small_balances | query · optional | Limite 1–50, 25 par défaut. Passe next_cursor comme before pour la page suivante. Omets before pour la page 1. has_balance=false et hide_small_balances=false incluent les soldes vides/faibles. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/wallets/YOUR_WALLET_ID/addresses" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/wallets/YOUR_WALLET_ID/addresses", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/wallets/YOUR_WALLET_ID/addresses");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/projects/YOUR_PROJECT_ID/wallets/YOUR_WALLET_ID/addresses",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"wallet": {
"id": "22222222-2222-4222-8222-222222222222",
"chain_slug": "ethereum"
},
"items": [],
"total": 0,
"total_pages": 1,
"next_cursor": null,
"reporting_currency": "EUR",
"has_balance": true,
"hide_small_balances": true,
"small_balance_threshold": {
"amount": "0.20",
"currency": "EUR"
}
}GETLister les identifiants commerçant/v1/operator/merchants/{merchant_id}/api-credentialsLecture seule
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite merchant_credentials.read ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| page, search | query · optional | Pages à partir de 1, 25 éléments par page. La recherche est prise en charge pour les commerçants, utilisateurs, projets, magasins, portefeuilles, identifiants et webhooks ; les listes d'événements natives utilisent leurs filtres propres. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"data": [],
"page": 1,
"page_size": 25,
"total": 0
}POSTCréer un identifiant commerçant/v1/operator/merchants/{merchant_id}/api-credentialsLecture + écriture
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite merchant_credentials.write ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Enregistre un Idempotency-Key unique et le corps exact avant l'envoi. Les nouvelles tentatives ne répètent jamais une action validée. Les champs secrets sont omis lors d'un rejeu ; si une réponse contenant un secret a été perdue, examine la ressource créée et renouvelle/réémets explicitement le secret. Un 409 operator_request_in_progress peut indiquer une requête interrompue au résultat inconnu : examine la ressource/l'audit ; ne réessaie pas aveuglément avec une nouvelle clé.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatoire | 16–128 lettres, chiffres, -, _ ou . ; enregistrée pour cette opération |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| name | string · required | Libellé d'une nouvelle clé commerçant ordinaire, pas une clé Opérateur. |
| access_level | read_only | read_write · default read_only | Lecture/écriture active le contrat API commerçant existant. |
| project_ids | UUID[] | Uniquement les projets du commerçant sélectionné ; une liste vide suit la politique existante couvrant tous ses projets. |
| enabled, ip_restriction_enabled, allowed_ips, requests_per_minute | optional | Contrôles existants des clés commerçant. Secret renvoyé une seule fois ; nécessite merchant_credentials.write. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Store integration",
"access_level": "read_only",
"enabled": true,
"ip_restriction_enabled": false,
"allowed_ips": [],
"project_ids": [
"11111111-1111-4111-8111-111111111111"
]
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"name": "Store integration",
"access_level": "read_only",
"enabled": true,
"ip_restriction_enabled": false,
"allowed_ips": [],
"project_ids": [
"11111111-1111-4111-8111-111111111111"
]
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"name": "Store integration",
"access_level": "read_only",
"enabled": true,
"ip_restriction_enabled": false,
"allowed_ips": [],
"project_ids": [
"11111111-1111-4111-8111-111111111111"
]
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"name": "Store integration",
"access_level": "read_only",
"enabled": true,
"ip_restriction_enabled": false,
"allowed_ips": [],
"project_ids": [
"11111111-1111-4111-8111-111111111111"
]
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 201 ou 200 application/json
{
"credential": {
"id": "22222222-2222-4222-8222-222222222222",
"name": "Checkout",
"access_level": "read_only",
"project_ids": [
"33333333-3333-4333-8333-333333333333"
],
"enabled": true
},
"token": "wc_live_EXAMPLE_ONLY_SAVE_ONCE"
}POSTMettre à jour un identifiant commerçant/v1/operator/merchants/{merchant_id}/api-credentials/{credential_id}Lecture + écriture
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite merchant_credentials.write ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Enregistre un Idempotency-Key unique et le corps exact avant l'envoi. Les nouvelles tentatives ne répètent jamais une action validée. Les champs secrets sont omis lors d'un rejeu ; si une réponse contenant un secret a été perdue, examine la ressource créée et renouvelle/réémets explicitement le secret. Un 409 operator_request_in_progress peut indiquer une requête interrompue au résultat inconnu : examine la ressource/l'audit ; ne réessaie pas aveuglément avec une nouvelle clé.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatoire | 16–128 lettres, chiffres, -, _ ou . ; enregistrée pour cette opération |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| credential_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| name, access_level, enabled, ip_restriction_enabled, allowed_ips | required fields | Envoie la configuration actuelle complète de l'identifiant avec les modifications. project_ids vaut [] par défaut ; requests_per_minute utilise par défaut le quota API commerçant. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Checkout",
"access_level": "read_only",
"enabled": true,
"ip_restriction_enabled": false,
"allowed_ips": [],
"project_ids": [
"33333333-3333-4333-8333-333333333333"
]
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"name": "Checkout",
"access_level": "read_only",
"enabled": true,
"ip_restriction_enabled": false,
"allowed_ips": [],
"project_ids": [
"33333333-3333-4333-8333-333333333333"
]
}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"name": "Checkout",
"access_level": "read_only",
"enabled": true,
"ip_restriction_enabled": false,
"allowed_ips": [],
"project_ids": [
"33333333-3333-4333-8333-333333333333"
]
}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"name": "Checkout",
"access_level": "read_only",
"enabled": true,
"ip_restriction_enabled": false,
"allowed_ips": [],
"project_ids": [
"33333333-3333-4333-8333-333333333333"
]
}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"id": "22222222-2222-4222-8222-222222222222",
"name": "Checkout",
"access_level": "read_only",
"project_ids": [
"33333333-3333-4333-8333-333333333333"
],
"enabled": true
}POSTRenouveler un identifiant commerçant/v1/operator/merchants/{merchant_id}/api-credentials/{credential_id}/rotateLecture + écriture
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite merchant_credentials.write ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Enregistre un Idempotency-Key unique et le corps exact avant l'envoi. Les nouvelles tentatives ne répètent jamais une action validée. Les champs secrets sont omis lors d'un rejeu ; si une réponse contenant un secret a été perdue, examine la ressource créée et renouvelle/réémets explicitement le secret. Un 409 operator_request_in_progress peut indiquer une requête interrompue au résultat inconnu : examine la ressource/l'audit ; ne réessaie pas aveuglément avec une nouvelle clé.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatoire | 16–128 lettres, chiffres, -, _ ou . ; enregistrée pour cette opération |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| credential_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID/rotate" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID/rotate", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID/rotate");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID/rotate",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"credential": {
"id": "22222222-2222-4222-8222-222222222222",
"name": "Checkout",
"access_level": "read_only",
"project_ids": [
"33333333-3333-4333-8333-333333333333"
],
"enabled": true
},
"token": "wc_live_EXAMPLE_ONLY_SAVE_ONCE"
}POSTRévoquer un identifiant commerçant/v1/operator/merchants/{merchant_id}/api-credentials/{credential_id}/revokeLecture + écriture
Gère ou examine la ressource nommée du commerçant hébergé avec un identifiant Opérateur distinct.
- Nécessite merchant_credentials.write ; uniquement les commerçants hébergés autorisés. Les clés Opérateur n'accèdent pas à l'espace du propre commerce du propriétaire.
- Enregistre un Idempotency-Key unique et le corps exact avant l'envoi. Les nouvelles tentatives ne répètent jamais une action validée. Les champs secrets sont omis lors d'un rejeu ; si une réponse contenant un secret a été perdue, examine la ressource créée et renouvelle/réémets explicitement le secret. Un 409 operator_request_in_progress peut indiquer une requête interrompue au résultat inconnu : examine la ressource/l'audit ; ne réessaie pas aveuglément avec une nouvelle clé.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_OPERATOR_API_TOKEN |
| Idempotency-Key | obligatoire | 16–128 lettres, chiffres, -, _ ou . ; enregistrée pour cette opération |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| merchant_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
| credential_id | path UUID | UUID canonique en minuscules de la ressource ; doit appartenir au périmètre commerçant de l'identifiant. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID/revoke" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: operator-request-1042' \
--header 'Content-Type: application/json' \
--data-raw '{}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{}`;
const response = await fetch("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID/revoke", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{}
JSON;
$ch = curl_init("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID/revoke");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: operator-request-1042", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "operator-request-1042",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{}""".encode("utf-8")
request = Request("https://api.example.com/v1/operator/merchants/YOUR_MERCHANT_ID/api-credentials/YOUR_CREDENTIAL_ID/revoke",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"revoked": true
}POSTVérifier un jeton d'invitation/v1/onboarding/invitations/checkPublic
Inscription par jeton uniquement. N'accepte pas de clé Opérateur et ne connecte pas automatiquement. La connexion console exige toujours le Basic Auth du site et le TOTP existant.
- Invitation de 48 heures ; lien de réinitialisation du mot de passe d'une heure. Jetons hachés à usage unique. Réémettre révoque le lien précédent. L'acceptation conserve TOTP et révoque les anciennes sessions.
- Aucune nouvelle tentative automatique. Si l'acceptation dépasse le délai, vérifie le statut du lien et essaie de te connecter ; ne suppose pas un échec. Limitation selon l'IP source observée. Le destinataire doit donner lui-même son accord sur la garde des fonds.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| token | string · required | Secret du fragment de l'URL d'invitation. Ne le journalise jamais. |
Requête
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/onboarding/invitations/check" \
--header 'Content-Type: application/json' \
--data-raw '{
"token": "YOUR_PRIVATE_INVITATION_TOKEN"
}'// Node.js 18+ · run on your server, never in browser code.
const body = `{
"token": "YOUR_PRIVATE_INVITATION_TOKEN"
}`;
const response = await fetch("https://api.example.com/v1/onboarding/invitations/check", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$body = <<<'JSON'
{
"token": "YOUR_PRIVATE_INVITATION_TOKEN"
}
JSON;
$ch = curl_init("https://api.example.com/v1/onboarding/invitations/check");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Content-Type": "application/json"
}
body = """{
"token": "YOUR_PRIVATE_INVITATION_TOKEN"
}""".encode("utf-8")
request = Request("https://api.example.com/v1/onboarding/invitations/check",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"kind": "invitation",
"email": "admin@example.test",
"merchant_name": "Example shop"
}POSTAccepter une invitation ou une réinitialisation de mot de passe/v1/onboarding/invitations/acceptPublic
Inscription par jeton uniquement. N'accepte pas de clé Opérateur et ne connecte pas automatiquement. La connexion console exige toujours le Basic Auth du site et le TOTP existant.
- Invitation de 48 heures ; lien de réinitialisation du mot de passe d'une heure. Jetons hachés à usage unique. Réémettre révoque le lien précédent. L'acceptation conserve TOTP et révoque les anciennes sessions.
- Aucune nouvelle tentative automatique. Si l'acceptation dépasse le délai, vérifie le statut du lien et essaie de te connecter ; ne suppose pas un échec. Limitation selon l'IP source observée. Le destinataire doit donner lui-même son accord sur la garde des fonds.
- Les exemples de réponse montrent certains champs. Considère les champs de réponse supplémentaires comme des ajouts.
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| token | string · required | Secret du fragment de l'URL d'invitation. Ne le journalise jamais. |
| password | string · required | Nouveau mot de passe, 12–128 caractères (512 octets UTF-8 maximum). |
| custody_acknowledged | boolean | Doit valoir true lors de l'acceptation d'une nouvelle invitation de portefeuille hébergé. |
Requête
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/onboarding/invitations/accept" \
--header 'Content-Type: application/json' \
--data-raw '{
"token": "YOUR_PRIVATE_INVITATION_TOKEN",
"password": "REPLACE_WITH_YOUR_OWN_UNIQUE_PASSWORD",
"custody_acknowledged": true
}'// Node.js 18+ · run on your server, never in browser code.
const body = `{
"token": "YOUR_PRIVATE_INVITATION_TOKEN",
"password": "REPLACE_WITH_YOUR_OWN_UNIQUE_PASSWORD",
"custody_acknowledged": true
}`;
const response = await fetch("https://api.example.com/v1/onboarding/invitations/accept", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$body = <<<'JSON'
{
"token": "YOUR_PRIVATE_INVITATION_TOKEN",
"password": "REPLACE_WITH_YOUR_OWN_UNIQUE_PASSWORD",
"custody_acknowledged": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/onboarding/invitations/accept");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Content-Type": "application/json"
}
body = """{
"token": "YOUR_PRIVATE_INVITATION_TOKEN",
"password": "REPLACE_WITH_YOUR_OWN_UNIQUE_PASSWORD",
"custody_acknowledged": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/onboarding/invitations/accept",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"password_set": true
}GETLister les exceptions de paiement/v1/projects/{project_id}/reconciliationLecture seule
Une file d'examen paginée unique pour les sous-paiements, surpaiements, paiements tardifs, réorganisés ou ambigus, livraisons échouées et moyens désactivés/expirés. Les cas reconnus par un opérateur se rouvrent à l'arrivée de nouvelles preuves.
- Lecture seule, limité au projet et couvert par le quota de l'identifiant. Les décisions financières et remboursements restent réservés à la console.
- Les lignes contiennent id (UUID interne), invoice_id (UUID public, identique aux callbacks), les informations du magasin, montant/devise fiat d'origine, invoice_status, état du cas, raisons, révision et updated_at. Utilise invoice_id dans l'endpoint de détail commerçant.
- La détection automatique suit la fenêtre de surveillance d'origine de la facture ; Relancer l'analyse prolonge l'observation d'une heure sans activer le paiement. Les moyens réglés/annulés continuent d'être surveillés dans cette fenêtre.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_MERCHANT_API_TOKEN |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| project_id | path UUID | Projet attribué à cet identifiant. |
| status | query string | open (par défaut), resolved ou all. |
| reason | query string | underpaid, overpaid, late, reorged, ambiguous, delivery_failed, disabled_method ou expired_method. |
| search | query string | Jusqu'à 100 caractères : identifiant de facture, commande, client ou magasin. |
| store_id | query UUID | Filtre de magasin facultatif. |
| page | query integer | 1–40001. 25 cas fixes par page. |
Réponse de la file d'exceptions
| Champ | Type | Présence | Description |
|---|---|---|---|
| data | ExceptionRow[] | toujours | Cas mis à jour le plus récemment en premier. Utilise invoice_id, pas l'id interne, dans les URL de détail commerçant. |
| pagination | object | toujours | page (1–40001), per_page (25), total des lignes correspondantes, has_more. |
| counts | object | toujours | Totaux open et resolved de tout le projet, indépendants des filtres actuels. |
ExceptionRow
| Champ | Type | Présence | Description |
|---|---|---|---|
| id / invoice_id | UUID | toujours | Identifiant interne de l'enregistrement / UUID de facture visible au client. invoice_id correspond aux payloads des callbacks. |
| store_id / store_name | UUID / string | toujours | Magasin propriétaire. |
| order_id / email | string | null | toujours | Référence de commande privée du commerçant et e-mail client. |
| amount / currency | decimal string / string | toujours | Montant et devise fiat d'origine de la facture. |
| invoice_status | invoice status | toujours | Statut actuel du cycle de vie du paiement. |
| status / reasons | open|resolved / string[] | toujours | État du cas et types d'exception listés dans le filtre reason. |
| revision / updated_at | integer / timestamp | toujours | Révision actuelle de l'examen et heure de mise à jour. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/reconciliation?status=open&page=1" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/reconciliation?status=open&page=1", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/reconciliation?status=open&page=1");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/reconciliation?status=open&page=1",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{"data":[],"pagination":{"page":1,"per_page":25,"total":0,"has_more":false},"counts":{"open":0,"resolved":0}}GETLire les preuves de rapprochement/v1/projects/{project_id}/reconciliation/{invoice_id}Lecture seule
Renvoie la facture, le cas, les totaux exacts par moyen et montants remboursables, transactions observées, historique des livraisons, décisions du commerçant et transferts de remboursement liés. N'expose jamais de clés de signature ni de secrets de callback.
- case est null lorsque la facture n'a pas généré d'exception. Les 100 observations et 50 livraisons les plus récentes sont renvoyées ; l'historique des décisions est paginé.
- refundable_atomic nécessite au moins une confirmation réseau, exclut les réservations de remboursement existantes et ne promet pas de fonds dépensables dans le portefeuille. Un devis actuel valide aussi la disponibilité du portefeuille, les soldes sources et les frais.
- Un remboursement diffusé signifie soumis à un endpoint blockchain, pas une réception par le client confirmée indépendamment. Les frais sont supplémentaires et les frais de traitement fiat ne sont pas automatiquement recrédités par l'émission d'un remboursement.
- Menu projet de la console → À vérifier propose annulation, acceptation, refus, réouverture, examen, notes, Relancer l'analyse, nouvelle tentative de livraison et remboursements sur les blockchains prises en charge. Les décisions utilisent des sessions protégées par CSRF, un request_id unique, la révision actuelle du cas, une note obligatoire et une confirmation explicite ; les jetons bearer ne peuvent pas invoquer ces modifications.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_MERCHANT_API_TOKEN |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| project_id | path UUID | Projet attribué. |
| invoice_id | path UUID | UUID public de facture, pas l'id interne. |
| page | query integer | Page d'historique des décisions, à partir de 1 ; 25 décisions par page. |
Réponse de rapprochement
| Champ | Type | Présence | Description |
|---|---|---|---|
| invoice | InvoiceDetail | toujours | Facture commerçant complète : champs récapitulatifs, métadonnées privées et payment_intents. Pas enveloppée dans data. |
| case | object | null | toujours | Cas actuel avec statut, raisons, révision et horodatages ; null sans exception. Les preuves internes sont exclues. |
| methods | object[] | toujours | id, wallet_id, asset_id, symbol, chain, decimals, expected_atomic, received_atomic, confirmed_atomic, refundable_atomic, address, tag, monitor_error, last_checked_at, monitoring_expires_at et spending_supported. Les montants atomiques sont des chaînes. |
| history | object[] | toujours | Les 25 décisions les plus récentes de cette page : id, action, note, actor, result, created_at. |
| history_pagination | object | toujours | page, per_page (25), total. Seul l'historique des décisions est paginé par page. |
| refunds | object[] | toujours | Les 100 remboursements les plus récents : id, payment_intent_id, amount_atomic, destination, status, request, treasury_intent_id, transfer_status, created_at et transactions (id/status). La soumission de remboursements est réservée à la console. |
| observations | object[] | toujours | Les 100 plus récents : payment_intent_id, transaction_id, event_index, amount, status, confirmations, observed_at, symbol, chain et disabled_at_detection. explorer_name/explorer_url sont inclus si pris en charge. |
| deliveries | object[] | toujours | Les 50 plus récentes : id, kind, status, attempts, response_status, error, next_attempt_at, event_type et created_at. Aucun secret de callback. |
Résumé de facture
| Champ | Type | Présence | Description |
|---|---|---|---|
| id | UUID | toujours | UUID interne de facture. Ne l'utilise pas dans les chemins de détail commerçant ni de paiement. |
| invoice_id | UUID | toujours | UUID public de facture utilisé dans les chemins de détail commerçant et de paiement. |
| project_id | UUID | toujours | Projet propriétaire. |
| store_id | UUID | toujours | Magasin propriétaire. |
| source | manual | api | toujours | Comment la facture a été créée. |
| order_id | string | null | toujours | Référence de commande du commerçant. |
| string | null | toujours | E-mail client réservé au commerçant. Jamais renvoyé par la page de paiement publique. | |
| customer_name | string | null | toujours | Nom d'affichage dérivé des métadonnées privées firstname, lastname et company. |
| customer_address | string | null | toujours | Adresse du commerçant sur une ligne dérivée des métadonnées privées company, street, street2, zip, city, country, countryiso2 et vatid. |
| description | string | null | toujours | Description visible par le client. |
| amount | decimal string | toujours | Montant canonique de la facture. |
| currency | string | toujours | Code normalisé de devise/actif de la facture. |
| exchange_rate_spread_percent | decimal string | toujours | Spread de devis verrouillé : la valeur définie à la création, ou celle du magasin par défaut si omise. Appliqué avant l'arrondi au supérieur ; ne change jamais pour cette facture. |
| underpayment_tolerance_percent | decimal string | toujours | Pourcentage immuable de manque accepté enregistré à la création de la facture. |
| status | invoice status | toujours | new, processing, settled, expired, invalid ou cancelled. |
| amount_status | amount status | toujours | none, partial, paid ou overpaid. Une facture de montant nul explicitement autorisée est réglée avec none et sans moyen de paiement. |
| timing_status | timing status | toujours | on_time ou late. |
| resolution | resolution | toujours | automatic, manually_settled ou manually_invalidated. |
| sequence | integer | toujours | Séquence monotone d'état de la facture, à partir de 1. |
| winning_payment_intent_id | UUID | null | toujours | Moyen de paiement ayant résolu la facture, lorsqu'il est sélectionné. |
| expires_at | RFC 3339 timestamp | toujours | Échéance du devis/paiement. |
| monitoring_expires_at | RFC 3339 timestamp | toujours | Dernière échéance configurée de surveillance tardive parmi les moyens de paiement. |
| settled_at | timestamp | null | toujours | Heure de règlement lorsqu'elle est réglée. |
| cancelled_at | timestamp | null | toujours | Heure d'annulation lorsqu'elle est annulée. |
| archived_at | timestamp | null | toujours | Heure d'archivage lorsqu'elle est archivée. |
| created_at | RFC 3339 timestamp | toujours | Heure de création. |
| updated_at | RFC 3339 timestamp | toujours | Heure de dernière mise à jour de l'état. |
Ajouts au détail de facture
| Champ | Type | Présence | Description |
|---|---|---|---|
| ipn_url | string | null | toujours | Cible IPN effective par facture. Réponse commerçant uniquement ; omise du paiement public. |
| redirect_url | string | null | toujours | URL de succès effective utilisée après règlement. |
| cancel_url | string | null | toujours | URL de retour effective utilisée lorsque le paiement se termine sans succès. |
| redirect_automatically | boolean | toujours | Indique si la page de paiement doit rediriger automatiquement après réussite. |
| checkout_language | string | toujours | Étiquette de langue effective de la page de paiement. |
| metadata | object | toujours | Métadonnées du commerçant. Jamais renvoyées par le paiement public. |
| payment_intents | PaymentIntent[] | toujours | Moyens de paiement chiffrés et état de surveillance. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/reconciliation/YOUR_PUBLIC_INVOICE_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/reconciliation/YOUR_PUBLIC_INVOICE_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/reconciliation/YOUR_PUBLIC_INVOICE_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/reconciliation/YOUR_PUBLIC_INVOICE_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{"invoice":{"invoice_id":"YOUR_PUBLIC_INVOICE_ID","status":"processing"},"case":{"status":"open","reasons":["underpaid"],"revision":1},"methods":[],"history":[],"history_pagination":{"page":1,"per_page":25,"total":0},"refunds":[],"observations":[],"deliveries":[]}GETDécouverte du service API/Public
Réponse de la couche externe de l'hôte API géré confirmant le rôle d'API publique v1. Cette réponse est produite par le proxy géré, pas par le routeur Axum du commerçant.
- Aucun jeton bearer n'est nécessaire.
- Seul le nom d'hôte API géré garantit cette réponse racine exacte.
Requête
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/"// Node.js 18+ · run on your server, never in browser code.
const response = await fetch("https://api.example.com/", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://api.example.com/");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://api.example.com/",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"service": "Wholly Crypto API",
"status": "ready",
"version": "v1"
}GETÉtat du service/healthzPublic
Vérifie l'accessibilité de l'application et un ping de base de données de deux secondes. À utiliser pour la surveillance, pas pour remplacer le statut de facture.
- Aucun jeton bearer n'est nécessaire.
- La valeur version est la version du paquet en cours d'exécution, pas celle du chemin API.
Requête
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/healthz"// Node.js 18+ · run on your server, never in browser code.
const response = await fetch("https://api.example.com/healthz", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://api.example.com/healthz");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://api.example.com/healthz",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 opérationnel ; 503 base de données indisponible
{
"status": "ok",
"database": "ok",
"version": "0.1.0"
}GETLister les actifs de paiement du projet/v1/projects/{project_id}/payment-assetsLecture seule
Liste les actifs natifs et tokens vérifiés avec la politique du projet, la disponibilité du portefeuille de blockchain et les capacités installées de détection/solde. scanner_ready vérifie la présence de l'adaptateur compilé, pas le quorum actuel des endpoints. Depuis 6.0.6, la création conserve les moyens configurés pendant l'arrêt du scanner. La vérification de réception exige toujours le seuil configuré de fournisseurs opérationnels au rôle exact (2 par défaut, 1 en option).
- Un token peut être listé globalement mais rester non sélectionnable lorsque scanner_ready ou payment_supported vaut false.
- La matrice de capacités de l'opérateur exige aussi le rôle exact d'endpoint du scanner ; un endpoint opérationnel servant une API incompatible n'est pas compté.
- Les tokens partagent le portefeuille de projet de leur blockchain native ; ils ne créent pas une autre phrase de récupération.
- Les résumés de portefeuille intégrés concernent uniquement la disponibilité et laissent les soldes vides ; utilise GET /v1/projects/{project_id}/wallets pour les soldes enrichis.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_MERCHANT_API_TOKEN |
| Accept | recommandé | application/json |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| project_id | path UUID | Projet activé attribué à l'identifiant. |
PaymentAsset
| Champ | Type | Présence | Description |
|---|---|---|---|
| id | UUID | toujours | Identifiant persistant d'actif de paiement utilisé par les routes de politique de projet et magasin. |
| asset_key | string | toujours | Identité canonique de l'actif natif ou du contrat au format CAIP. |
| chain_slug / network | string | toujours | Identifiant de blockchain Wholly Crypto et réseau configuré. |
| caip_network_id / caip_asset_id | string / string|null | toujours | Identités canoniques de réseau et d'actif. |
| asset_kind | native | token | toujours | Indique si le règlement utilise la monnaie de la blockchain ou un contrat/mint vérifié. |
| payment_rail | string | toujours | Canal d'exécution : utxo, evm-native, solana-native, account-native, privacy-native ou token-transfer. |
| symbol / name / decimals | string / string / integer | toujours | Identité d'affichage et précision exacte en unités atomiques. |
| contract_address | string | null | toujours | Contrat ERC-20 ou mint SPL canonique pour les tokens ; null pour les actifs natifs. |
| coingecko_id | string | null | toujours | Identité de découverte/prix. Null pour les contrats personnalisés ; ne déduis jamais un prix de marché de leur symbole. Les métadonnées CoinGecko seules ne rendent jamais un token sélectionnable. |
| custom_token | boolean | toujours | Contrat personnalisé vérifié on-chain, avec prix fixe en USD ou pool DEX sélectionné au niveau du projet. |
| icon_path | path | null | toujours | Icône du token en cache local, si disponible. |
| token_standard | erc20 | spl-token | null | toujours | Standard de token vérifié à l'exécution ; null pour les actifs natifs. |
| metadata_verified_at | timestamp | null | toujours | Heure de vérification des métadonnées on-chain pour les tokens promus. |
| payment_supported / scanner_ready / balance_ready | boolean | toujours | Conditions du registre à la compilation. scanner_ready signifie que le scanner de paiement est installé ; la confirmation exige le nombre configuré de fournisseurs opérationnels au rôle exact (2 par défaut, 1 en option) ; l'indisponibilité temporaire du scanner ne bloque pas la création de factures depuis 6.0.6. balance_ready vaut true uniquement pour les adaptateurs de solde implémentés. |
| default_finality_mode | confirmations | finalized | toujours | Modèle de finalité par défaut hérité par une nouvelle politique de projet. |
| default_required_confirmations / default_monitoring_minutes | integer | toujours | Politique de confirmation et de surveillance par défaut. |
ProjectPaymentAsset
| Champ | Type | Présence | Description |
|---|---|---|---|
| asset | PaymentAsset | toujours | Actif natif ou token vérifié persistant. |
| policy | ProjectAssetPolicy | null | toujours | Politique d'activation/finalité du projet, ou null si non configurée. Inclut custom_price_mode (fixed/dex), custom_price_usd (chaîne décimale fixe ou null), custom_dex_pair (pool sélectionné ou null) et custom_dex (dex_id, quote_symbol, price_usd actuel ou null, liquidity_usd, fetched_at, last_error). Les prix personnalisés sont partagés entre les magasins du projet. |
| wallet | WalletSummary | null | toujours | Portefeuille de projet sans garde de la blockchain. Les tokens partagent le portefeuille natif de leur blockchain. |
| wallet_readiness | readiness enum | toujours | unsupported, project_disabled, project_asset_disabled, store_disabled, store_asset_disabled, wallet_missing, wallet_pending, wallet_disabled, wallet_error, backup_required, account_activation_required, external_wallet_rpc_required ou ready. |
| receive_readiness | ReceiveReadiness | null | 5.5.0+ | Évaluation partagée de la configuration de réception du projet. Inclut les contrôles du portefeuille et des fournisseurs de détection indépendants, séparés de la fraîcheur des soldes et du gas d'envoi. Null si aucune politique de projet n'existe. La devise/les taux sont vérifiés à la création d'une facture. |
WalletSummary
| Champ | Type | Présence | Description |
|---|---|---|---|
| id / project_id / native_asset_id | UUID | toujours | Identifiants du portefeuille, du projet propriétaire et de l'actif natif de la blockchain. |
| chain_slug / network | string | toujours | Blockchain et réseau du portefeuille. |
| asset_symbol / asset_name | string | toujours | Identité d'affichage de l'actif natif de la blockchain. |
| status | pending | active | disabled | error | toujours | État opérationnel du portefeuille. |
| label | string | toujours | Libellé de l'opérateur. |
| public_key / primary_address | string | null | toujours | Identité publique du portefeuille ; aucune phrase de récupération ni clé privée n'est exposée. |
| derivation_scheme / address_format | string | null | toujours | Politique et format des adresses. |
| backup_confirmed_at | timestamp | null | toujours | Non null après confirmation de la sauvegarde de récupération par l'opérateur. |
| activation_required / activation_verified_at | boolean / timestamp|null | toujours | Les comptes partagés XRP et Stellar restent indisponibles jusqu'à ce que l'opérateur alimente l'adresse affichée et que les fournisseurs de détection configurés vérifient ce compte exact. La preuve persistante n'expire pas ; l'état actuel des scanners est contrôlé séparément pour vérifier les paiements, pas pour créer des factures. |
| receive_readiness | ReceiveReadiness | null | 5.5.0+ | Inclus dans les listes de portefeuilles : configuration de réception du projet et prérequis des scanners de blockchain. Distinct des soldes, du gas des tokens et de la disponibilité d'envoi. D'autres réponses de portefeuille peuvent le laisser null. |
| monero_wallet_rpc | MoneroWalletRpcBinding | null | toujours | État de liaison wallet-RPC externe en consultation seule pour Monero, épuré des données sensibles. Inclut endpoint, mode d'authentification, adresse principale du compte 0, indicateurs/hauteurs de preuves techniques et horodatages des attestations de l'opérateur ; les identifiants, clés et fichiers de portefeuille ne sont jamais sérialisés. |
| last_secret_revealed_at / secret_reveal_count | timestamp|null / integer | toujours | Métadonnées d'audit de divulgation des secrets côté console. |
| next_receive_index | integer | toujours | Indice de la prochaine adresse enfant réservée. |
| last_scanned_height / last_scanned_at / last_error | integer|null / timestamp|null / string|null | toujours | État du scanner de portefeuille. |
| balances | WalletAssetBalance[] | toujours | Soldes en cache pour chacun des 30 canaux natifs, plus les actifs ERC-20 et SPL vérifiés. Un wallet-RPC externe configuré en consultation seule est nécessaire pour Monero. |
| total_value_usd | decimal string | null | toujours | Somme indicative des soldes avec un prix USD actuel. |
| balance_status | pending | refreshing | fresh | stale | error | unknown | toujours | Fraîcheur agrégée du cache ; unknown est une valeur de repli prudente et aucun de ces états ne prouve le règlement d'une facture. |
| balance_checked_at | timestamp | null | toujours | Plus ancien contrôle de solde réussi pertinent représenté par l'agrégat. |
| recent_payments | WalletRecentPayment[] | toujours | Jusqu'aux trois observations valides les plus récentes detected, confirming ou final attribuées à ce portefeuille exact. |
| created_at / updated_at | RFC 3339 timestamp | toujours | Heure de création et de dernière mise à jour du portefeuille. |
ReceiveReadiness
| Champ | Type | Présence | Description |
|---|---|---|---|
| ready | boolean | toujours | Les contrôles de configuration de réception réussissent. Ne décrit pas la disponibilité de dépense, le gas, l'actualisation des soldes ni un devis futur garanti. |
| invoice_creatable | boolean | 6.0.6+ | La configuration permet un moyen de facture malgré des avertissements temporaires du scanner. Le prix de devise est vérifié à la création. Ce n'est pas une vérification de paiement : ready peut être false alors que invoice_creatable est true. Les portefeuilles manquants, politiques désactivées et adaptateurs non pris en charge restent bloquants par sécurité. |
| checked_at | timestamp | toujours | Heure d'évaluation. Une liste ne lance aucune requête réseau et n'alloue aucune adresse. |
| issues | PaymentMethodIssue[] | toujours | Vide si prêt ; sinon, avertissement de réception ou blocage de configuration. Vérifie invoice_creatable pour distinguer les avertissements temporaires du scanner des échecs de configuration de facture. |
PaymentMethodIssue
| Champ | Type | Présence | Description |
|---|---|---|---|
| chain_slug / asset_id / asset_ticker | string / UUID / string | si connu | Identifie la blockchain et l'actif concernés. Lightning peut omettre asset_id. |
| reason_code | string | toujours | scanner_provider_quorum, scanner_not_checked, scanner_unavailable, wallet_missing, wallet_disabled, wallet_backup_required, wallet_key_unavailable, wallet_activation_required, monero_binding_unavailable, rate_unavailable, custom_rate_unavailable, lightning_unavailable, project_disabled, store_disabled, chain_disabled, asset_disabled ou asset_not_accepted. |
| message / action | string | si disponible | Explication pour le commerçant et identifiant d'action : chain_connections, wallets, rates, payment_methods, project_settings ou store_settings. Aucun identifiant ni URL privée de fournisseur. |
| required_endpoint_role | string | null | on-chain | Rôle API de scanner préféré (ancien champ). Utilise accepted_endpoint_roles pour la liste complète de compatibilité. L'état de base du nœud ne prouve pas la prise en charge de l'historique des paiements. |
| accepted_endpoint_roles | string[] | null | on-chain | Dialectes API compatibles, pas une preuve d'historique ou de capacité d'endpoint. node-rpc brut prend en charge BTC/BCH/LTC/DOGE/DASH et ZEC transparent (blocs décodés complets, 1–48 confirmations), TRX natif solidifié, ALGO natif via algod, XTZ via Octez, DOT Asset Hub finalisé via métadonnées SCALE et XLM natif via Stellar RPC avec identifiant de mémo de facture. Un historique élagué ou incomplet n'est pas admissible. Ces adaptateurs bruts n'ajoutent pas de canaux de tokens. Les API indexées restent des alternatives ; voir le tableau ci-dessous. Les sources brutes/indexées mixtes vérifient indépendamment des fenêtres limitées ; deux fournisseurs indépendants restent le défaut, pas des alias d'un même opérateur. La hauteur de base d'un nœud, les informations de blockchain ORDnet et un relais EVM pour un canal non EVM ne sont pas des preuves de réception. Monero nécessite toujours un wallet-RPC en consultation seule lié au projet. |
| healthy_endpoints | integer | on-chain | Endpoints correspondants opérationnels, pas le nombre de fournisseurs indépendants. |
| usable_independent_providers / required_independent_providers | integer | on-chain | Emplacements de vérification utilisables, limités à deux. required_independent_providers est le réglage de blockchain : 2 par défaut, ou 1 après choix explicite de l'administrateur. Le mode à deux fournisseurs exige des clés fournisseur ET des hôtes différents. Les sources désactivées, périmées (plus de dix minutes) ou en pause ne remplissent pas d'emplacement. Lightning utilise ses propres règles de connexion. |
| last_checked_at | timestamp | null | on-chain | Dernier contrôle d'état de l'endpoint correspondant, distinct de l'heure d'évaluation. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-assets" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Accept: application/json'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-assets", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
"Accept": "application/json"
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-assets");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Accept: application/json"],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Accept": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-assets",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"data": [
{
"asset": {
"id": "10000000-0000-4000-8000-000000000003",
"asset_key": "eip155:1/slip44:60",
"chain_slug": "ethereum",
"network": "mainnet",
"caip_network_id": "eip155:1",
"caip_asset_id": "eip155:1/slip44:60",
"asset_kind": "native",
"payment_rail": "evm-native",
"symbol": "ETH",
"name": "Ethereum",
"decimals": 18,
"contract_address": null,
"coingecko_id": "ethereum",
"icon_path": "/assets/coingecko/ethereum.png",
"token_standard": null,
"metadata_verified_at": null,
"payment_supported": true,
"scanner_ready": true,
"balance_ready": true,
"default_finality_mode": "confirmations",
"default_required_confirmations": 12,
"default_monitoring_minutes": 60
},
"policy": {
"enabled": true,
"finality_mode": "confirmations",
"required_confirmations": 12,
"monitoring_minutes": 60,
"late_monitoring_days": 30
},
"wallet": null,
"wallet_readiness": "wallet_missing",
"receive_readiness": { "ready": false, "invoice_creatable": false, "checked_at": "2026-09-16T09:00:00Z", "issues": [{ "chain_slug": "ethereum", "asset_id": "10000000-0000-4000-8000-000000000003", "asset_ticker": "ETH", "reason_code": "wallet_missing", "message": "ethereum / ETH: Create a project wallet for this chain.", "action": "wallets" }] }
}
]
}PUTMettre à jour la politique d'actifs du projet/v1/projects/{project_id}/payment-assets/{asset_id}Lecture + écriture
Crée ou remplace la politique du projet pour un actif persistant et renvoie la liste actualisée des actifs du projet. Désactiver une blockchain native rend son actif natif et ses tokens indisponibles pour les nouvelles factures, mais conserve les politiques de tokens, portefeuilles et sélections des magasins pour les reprendre plus tard.
- Le corps remplace entièrement la politique et refuse les champs inconnus.
- L'activation dans le projet ne sélectionne pas à elle seule l'actif pour un magasin.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_MERCHANT_API_TOKEN |
| Content-Type | obligatoire | application/json |
| Accept | recommandé | application/json |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| project_id | path UUID | Projet activé attribué à l'identifiant. |
| asset_id | path UUID | Identifiant d'actif renvoyé par la liste d'actifs du projet ou l'enregistrement du token. |
Mise à jour de la politique d'actifs du projet
| Champ | Type | Présence | Description |
|---|---|---|---|
| enabled | boolean | obligatoire | Active ou désactive l'actif pour le projet. La blockchain native doit être activée avant tout token. |
| finality_mode | confirmations | finalized | obligatoire | Politique de finalité prise en charge par le canal de l'actif. finalized nécessite required_confirmations=1. |
| required_confirmations | integer | obligatoire | Les canaux Bitcoin et EVM acceptent zéro ; les autres canaux à confirmations en exigent au moins une, ceux limités à finalized en exigent exactement une et les canaux EVM sont limités à 0–48 pour que chaque transfert reste dans la fenêtre de relecture des transactions. |
| monitoring_minutes | integer | obligatoire | Fenêtre d'interrogation de 1–10 080 minutes pendant qu'une facture est active. |
| late_monitoring_days | integer | obligatoire | 0–3 650 jours de surveillance après expiration de la facture. |
PaymentAsset
| Champ | Type | Présence | Description |
|---|---|---|---|
| id | UUID | toujours | Identifiant persistant d'actif de paiement utilisé par les routes de politique de projet et magasin. |
| asset_key | string | toujours | Identité canonique de l'actif natif ou du contrat au format CAIP. |
| chain_slug / network | string | toujours | Identifiant de blockchain Wholly Crypto et réseau configuré. |
| caip_network_id / caip_asset_id | string / string|null | toujours | Identités canoniques de réseau et d'actif. |
| asset_kind | native | token | toujours | Indique si le règlement utilise la monnaie de la blockchain ou un contrat/mint vérifié. |
| payment_rail | string | toujours | Canal d'exécution : utxo, evm-native, solana-native, account-native, privacy-native ou token-transfer. |
| symbol / name / decimals | string / string / integer | toujours | Identité d'affichage et précision exacte en unités atomiques. |
| contract_address | string | null | toujours | Contrat ERC-20 ou mint SPL canonique pour les tokens ; null pour les actifs natifs. |
| coingecko_id | string | null | toujours | Identité de découverte/prix. Null pour les contrats personnalisés ; ne déduis jamais un prix de marché de leur symbole. Les métadonnées CoinGecko seules ne rendent jamais un token sélectionnable. |
| custom_token | boolean | toujours | Contrat personnalisé vérifié on-chain, avec prix fixe en USD ou pool DEX sélectionné au niveau du projet. |
| icon_path | path | null | toujours | Icône du token en cache local, si disponible. |
| token_standard | erc20 | spl-token | null | toujours | Standard de token vérifié à l'exécution ; null pour les actifs natifs. |
| metadata_verified_at | timestamp | null | toujours | Heure de vérification des métadonnées on-chain pour les tokens promus. |
| payment_supported / scanner_ready / balance_ready | boolean | toujours | Conditions du registre à la compilation. scanner_ready signifie que le scanner de paiement est installé ; la confirmation exige le nombre configuré de fournisseurs opérationnels au rôle exact (2 par défaut, 1 en option) ; l'indisponibilité temporaire du scanner ne bloque pas la création de factures depuis 6.0.6. balance_ready vaut true uniquement pour les adaptateurs de solde implémentés. |
| default_finality_mode | confirmations | finalized | toujours | Modèle de finalité par défaut hérité par une nouvelle politique de projet. |
| default_required_confirmations / default_monitoring_minutes | integer | toujours | Politique de confirmation et de surveillance par défaut. |
ProjectPaymentAsset
| Champ | Type | Présence | Description |
|---|---|---|---|
| asset | PaymentAsset | toujours | Actif natif ou token vérifié persistant. |
| policy | ProjectAssetPolicy | null | toujours | Politique d'activation/finalité du projet, ou null si non configurée. Inclut custom_price_mode (fixed/dex), custom_price_usd (chaîne décimale fixe ou null), custom_dex_pair (pool sélectionné ou null) et custom_dex (dex_id, quote_symbol, price_usd actuel ou null, liquidity_usd, fetched_at, last_error). Les prix personnalisés sont partagés entre les magasins du projet. |
| wallet | WalletSummary | null | toujours | Portefeuille de projet sans garde de la blockchain. Les tokens partagent le portefeuille natif de leur blockchain. |
| wallet_readiness | readiness enum | toujours | unsupported, project_disabled, project_asset_disabled, store_disabled, store_asset_disabled, wallet_missing, wallet_pending, wallet_disabled, wallet_error, backup_required, account_activation_required, external_wallet_rpc_required ou ready. |
| receive_readiness | ReceiveReadiness | null | 5.5.0+ | Évaluation partagée de la configuration de réception du projet. Inclut les contrôles du portefeuille et des fournisseurs de détection indépendants, séparés de la fraîcheur des soldes et du gas d'envoi. Null si aucune politique de projet n'existe. La devise/les taux sont vérifiés à la création d'une facture. |
ReceiveReadiness
| Champ | Type | Présence | Description |
|---|---|---|---|
| ready | boolean | toujours | Les contrôles de configuration de réception réussissent. Ne décrit pas la disponibilité de dépense, le gas, l'actualisation des soldes ni un devis futur garanti. |
| invoice_creatable | boolean | 6.0.6+ | La configuration permet un moyen de facture malgré des avertissements temporaires du scanner. Le prix de devise est vérifié à la création. Ce n'est pas une vérification de paiement : ready peut être false alors que invoice_creatable est true. Les portefeuilles manquants, politiques désactivées et adaptateurs non pris en charge restent bloquants par sécurité. |
| checked_at | timestamp | toujours | Heure d'évaluation. Une liste ne lance aucune requête réseau et n'alloue aucune adresse. |
| issues | PaymentMethodIssue[] | toujours | Vide si prêt ; sinon, avertissement de réception ou blocage de configuration. Vérifie invoice_creatable pour distinguer les avertissements temporaires du scanner des échecs de configuration de facture. |
PaymentMethodIssue
| Champ | Type | Présence | Description |
|---|---|---|---|
| chain_slug / asset_id / asset_ticker | string / UUID / string | si connu | Identifie la blockchain et l'actif concernés. Lightning peut omettre asset_id. |
| reason_code | string | toujours | scanner_provider_quorum, scanner_not_checked, scanner_unavailable, wallet_missing, wallet_disabled, wallet_backup_required, wallet_key_unavailable, wallet_activation_required, monero_binding_unavailable, rate_unavailable, custom_rate_unavailable, lightning_unavailable, project_disabled, store_disabled, chain_disabled, asset_disabled ou asset_not_accepted. |
| message / action | string | si disponible | Explication pour le commerçant et identifiant d'action : chain_connections, wallets, rates, payment_methods, project_settings ou store_settings. Aucun identifiant ni URL privée de fournisseur. |
| required_endpoint_role | string | null | on-chain | Rôle API de scanner préféré (ancien champ). Utilise accepted_endpoint_roles pour la liste complète de compatibilité. L'état de base du nœud ne prouve pas la prise en charge de l'historique des paiements. |
| accepted_endpoint_roles | string[] | null | on-chain | Dialectes API compatibles, pas une preuve d'historique ou de capacité d'endpoint. node-rpc brut prend en charge BTC/BCH/LTC/DOGE/DASH et ZEC transparent (blocs décodés complets, 1–48 confirmations), TRX natif solidifié, ALGO natif via algod, XTZ via Octez, DOT Asset Hub finalisé via métadonnées SCALE et XLM natif via Stellar RPC avec identifiant de mémo de facture. Un historique élagué ou incomplet n'est pas admissible. Ces adaptateurs bruts n'ajoutent pas de canaux de tokens. Les API indexées restent des alternatives ; voir le tableau ci-dessous. Les sources brutes/indexées mixtes vérifient indépendamment des fenêtres limitées ; deux fournisseurs indépendants restent le défaut, pas des alias d'un même opérateur. La hauteur de base d'un nœud, les informations de blockchain ORDnet et un relais EVM pour un canal non EVM ne sont pas des preuves de réception. Monero nécessite toujours un wallet-RPC en consultation seule lié au projet. |
| healthy_endpoints | integer | on-chain | Endpoints correspondants opérationnels, pas le nombre de fournisseurs indépendants. |
| usable_independent_providers / required_independent_providers | integer | on-chain | Emplacements de vérification utilisables, limités à deux. required_independent_providers est le réglage de blockchain : 2 par défaut, ou 1 après choix explicite de l'administrateur. Le mode à deux fournisseurs exige des clés fournisseur ET des hôtes différents. Les sources désactivées, périmées (plus de dix minutes) ou en pause ne remplissent pas d'emplacement. Lightning utilise ses propres règles de connexion. |
| last_checked_at | timestamp | null | on-chain | Dernier contrôle d'état de l'endpoint correspondant, distinct de l'heure d'évaluation. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request PUT \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-assets/YOUR_ASSET_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Content-Type: application/json' \
--data-raw '{
"enabled": true,
"finality_mode": "confirmations",
"required_confirmations": 2,
"monitoring_minutes": 60,
"late_monitoring_days": 30
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const body = `{
"enabled": true,
"finality_mode": "confirmations",
"required_confirmations": 2,
"monitoring_minutes": 60,
"late_monitoring_days": 30
}`;
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-assets/YOUR_ASSET_ID", {
method: "PUT",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$body = <<<'JSON'
{
"enabled": true,
"finality_mode": "confirmations",
"required_confirmations": 2,
"monitoring_minutes": 60,
"late_monitoring_days": 30
}
JSON;
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-assets/YOUR_ASSET_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"enabled": true,
"finality_mode": "confirmations",
"required_confirmations": 2,
"monitoring_minutes": 60,
"late_monitoring_days": 30
}""".encode("utf-8")
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-assets/YOUR_ASSET_ID",
method="PUT", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"data": [
{ "asset": { "id": "ASSET_UUID", "symbol": "USDC", "asset_kind": "token", "scanner_ready": true }, "policy": { "enabled": true, "finality_mode": "confirmations", "required_confirmations": 2, "monitoring_minutes": 60, "late_monitoring_days": 30 }, "wallet_readiness": "ready" }
]
}GETParcourir les tokens candidats aux paiements/v1/projects/{project_id}/payment-token-candidatesLecture seule
Recherche les correspondances de contrats CoinGecko en cache local uniquement sur les blockchains dont le scanner de factures token et l'adaptateur de solde sont implémentés. Les résultats sont des candidats de découverte, pas des actifs de paiement de confiance.
- Adaptateurs de tokens pris en charge : ERC-20 sur Ethereum, Base, BNB Chain, HyperEVM, Avalanche, Polygon, Arbitrum et Optimism ; SPL sur Solana.
- Les blockchains du catalogue non prises en charge sont refusées au lieu d'apparaître sélectionnables.
- Le classement, l'icône et le prix CoinGecko sont des données de découverte indicatives.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_MERCHANT_API_TOKEN |
| Accept | recommandé | application/json |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| project_id | path UUID | Projet activé attribué à l'identifiant. |
| chain_slug | query string | Slug obligatoire d'une blockchain EVM prise en charge ou solana. |
| q | query string | Sous-chaîne facultative de nom, symbole, identifiant CoinGecko, contrat ou mint ; 80 caractères maximum. |
| limit | query integer | Facultatif 1–100 ; 50 par défaut. |
TokenCandidate
| Champ | Type | Présence | Description |
|---|---|---|---|
| coingecko_id | string | toujours | Identité de découverte CoinGecko utilisée par la requête d'enregistrement. |
| chain_slug | string | toujours | Blockchain Wholly Crypto correspondante. |
| symbol / name | string | toujours | Identité d'affichage du catalogue. |
| contract_address | string | toujours | Contrat ou mint correspondant ; vérifié on-chain avant l'enregistrement. |
| market_cap_rank | integer | null | toujours | Classement de découverte, pas un indicateur de confiance ni de disponibilité de paiement. |
| icon_path | path | toujours | Chemin de l'icône CoinGecko en cache local. |
| current_price_usd | decimal string | null | toujours | Prix USD indicatif en cache. |
| token_standard | erc20 | spl-token | toujours | Standard de token pris en charge par l'adaptateur de la blockchain sélectionnée. |
| scanner_ready | boolean | toujours | True uniquement pour les candidats d'un canal de tokens implémenté dans cette compilation. |
| registered_asset_id | UUID | null | toujours | Actif persistant existant si déjà promu. |
| project_enabled | boolean | toujours | Indique si l'actif enregistré est activé pour ce projet. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-candidates?chain_slug=ethereum&q=USDC&limit=50" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-candidates?chain_slug=ethereum&q=USDC&limit=50", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-candidates?chain_slug=ethereum&q=USDC&limit=50");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-candidates?chain_slug=ethereum&q=USDC&limit=50",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"data": [
{
"coingecko_id": "usd-coin",
"chain_slug": "ethereum",
"symbol": "USDC",
"name": "USDC",
"contract_address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"market_cap_rank": 7,
"icon_path": "/assets/coingecko/usd-coin.png",
"current_price_usd": "1.0001",
"token_standard": "erc20",
"scanner_ready": true,
"registered_asset_id": null,
"project_enabled": false
}
]
}POSTVérifier et enregistrer un token/v1/projects/{project_id}/payment-token-assetsLecture + écriture
Promeut un candidat actuel dans le registre persistant de paiements uniquement après vérification par les nœuds configurés de l'identité de blockchain, du contrat/mint, des décimales et d'une requête de solde utilisable. L'enregistrement ne fait jamais confiance aux seules métadonnées CoinGecko et chaque projet est limité à 20 actifs de tokens enregistrés.
- Active l'actif natif de la blockchain dans le projet avant d'enregistrer ses tokens.
- Un projet peut enregistrer au maximum 20 actifs de tokens ; un nouveau candidat au-delà renvoie token_chain_not_ready (409). Réutiliser un actif déjà enregistré ne consomme pas un autre emplacement.
- La vérification des nœuds peut être plus longue qu'une lecture du catalogue ; utilise un délai explicite côté client.
- Après l'enregistrement, sélectionne l'actif pour chaque magasin qui doit le proposer.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_MERCHANT_API_TOKEN |
| Content-Type | obligatoire | application/json |
| Accept | recommandé | application/json |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| project_id | path UUID | Projet activé attribué à l'identifiant. |
Corps d'enregistrement du token
| Champ | Type | Présence | Description |
|---|---|---|---|
| chain_slug | string | obligatoire | ethereum, base, bnb-chain, hyperliquid, avalanche, polygon, arbitrum, optimism ou solana. |
| coingecko_id | string | obligatoire | Identité exacte du candidat renvoyée par la recherche de tokens. Conserve les traits de soulignement ou tirets initiaux, comme _ ou -6. Ne déduis pas cet identifiant du nom ou symbole du token. |
| enabled | boolean | facultatif | État de la politique du projet après vérification ; true par défaut. |
RegisteredTokenAsset
| Champ | Type | Présence | Description |
|---|---|---|---|
| asset_id | UUID | toujours | Identifiant persistant de l'actif de paiement. |
| chain_slug / coingecko_id | string | toujours | Blockchain vérifiée et identité de découverte/prix conservée. |
| contract_address | string | toujours | Contrat ou mint canonique vérifié. |
| token_standard | erc20 | spl-token | toujours | Standard de token à l'exécution vérifié. |
| symbol / name / decimals | string / string / integer | toujours | Identité d'affichage promue et précision exacte. |
| enabled | boolean | toujours | État initial de la politique du projet. |
| metadata_verified_at | RFC 3339 timestamp | toujours | Heure de vérification on-chain. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-assets" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Content-Type: application/json' \
--data-raw '{
"chain_slug": "ethereum",
"coingecko_id": "usd-coin",
"enabled": true
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const body = `{
"chain_slug": "ethereum",
"coingecko_id": "usd-coin",
"enabled": true
}`;
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-assets", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$body = <<<'JSON'
{
"chain_slug": "ethereum",
"coingecko_id": "usd-coin",
"enabled": true
}
JSON;
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-assets");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"chain_slug": "ethereum",
"coingecko_id": "usd-coin",
"enabled": true
}""".encode("utf-8")
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-assets",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 201 application/json
{
"data": {
"asset_id": "44444444-4444-4444-8444-444444444444",
"chain_slug": "ethereum",
"coingecko_id": "usd-coin",
"contract_address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"token_standard": "erc20",
"symbol": "USDC",
"name": "USDC",
"decimals": 6,
"enabled": true,
"metadata_verified_at": "2026-08-31T18:00:00Z"
}
}GETTrouver les pools DEX d'un token personnalisé/v1/projects/{project_id}/payment-token-dex-poolsLecture seule
Trouve jusqu'à 12 pools éligibles par blockchain et contrat exact du token de base via DEX Screener, triés par liquidité. Cela n'enregistre ni n'active de token.
- Un tableau data vide signifie qu'aucun pool admissible n'a été trouvé. Seuls les pools où le contrat exact demandé est le token de base sont renvoyés ; les prix USD du token de cotation ne sont jamais supposés.
- La présence sur un DEX n'est pas un audit de sécurité. La liquidité minimale et l'activité récente réduisent les prix inutilisables mais n'empêchent pas la manipulation du marché.
- Uniswap, PancakeSwap et d'autres DEX indexés sont pris en charge là où le scanner existant de blockchain prend en charge les tokens. L'accès API reste limité au projet et au débit autorisé. Les appels aux fournisseurs sont aussi sérialisés et limités.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_MERCHANT_API_TOKEN |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| project_id | path UUID | Projet attribué. |
| chain_slug | query string | Blockchain EVM de tokens prise en charge ou solana. |
| contract_address | query string | Contrat ERC-20 exact ou mint SPL classique. |
CustomDexPool
| Champ | Type | Présence | Description |
|---|---|---|---|
| pair_address / dex_id / quote_symbol | string | toujours | Identifiant exact du pool, identifiant d'échange (par ex. uniswap/pancakeswap) et symbole apparié uniquement pour l'affichage. |
| price_usd / liquidity_usd | decimal string | toujours | Prix USD du token de base demandé et liquidité totale du pool. Au moins $10,000 de liquidité et un échange dans la dernière heure sont requis. |
| fetched_at | RFC 3339 timestamp | toujours | Moment où le serveur a récupéré l'observation du fournisseur, pas l'horodatage d'un échange on-chain. |
| url | HTTPS URL | toujours | Lien DEX Screener validé vers ce pool. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-dex-pools?chain_slug=ethereum&contract_address=YOUR_TOKEN_CONTRACT" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-dex-pools?chain_slug=ethereum&contract_address=YOUR_TOKEN_CONTRACT", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-dex-pools?chain_slug=ethereum&contract_address=YOUR_TOKEN_CONTRACT");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-dex-pools?chain_slug=ethereum&contract_address=YOUR_TOKEN_CONTRACT",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{"data":[{"pair_address":"0x2222222222222222222222222222222222222222","dex_id":"uniswap","quote_symbol":"WETH","price_usd":"0.25","liquidity_usd":"250000.00","fetched_at":"2026-09-09T12:00:00Z","url":"https://dexscreener.com/ethereum/0x2222222222222222222222222222222222222222"}]}POSTAjouter ou modifier le prix d'un token personnalisé/v1/projects/{project_id}/payment-token-assets/customLecture + écriture
Vérifie un contrat personnalisé avec les nœuds de blockchain configurés et l'enregistre sans exiger sa présence sur CoinGecko. Le prix fixe en USD ou le pool DEX automatique sélectionné appartient à ce projet, pas au symbole ni aux autres projets. Répéter la même identité met à jour son prix de projet sans changer une politique d'activation/désactivation existante.
- Après l'enregistrement, sélectionne asset_id dans l'endpoint payment-assets du magasin ; l'enregistrement seul n'active jamais un moyen du magasin.
- Les tokens personnalisés et du catalogue partagent la limite de 20 tokens par projet. Le même contrat sur des blockchains différentes constitue un actif de paiement distinct.
- Les contrats existants du catalogue renvoient 409 : utilise l'enregistrement du catalogue pour conserver les taux de marché automatiques. Un symbole personnalisé n'emprunte jamais le prix d'un token homonyme.
- Les prix fixes sont des estimations de l'opérateur. Les prix DEX automatiques sont des observations au comptant du pool sélectionné via DEX Screener, pas un oracle résistant aux manipulations. Le spread du magasin et l'arrondi au supérieur s'appliquent toujours, avec des taux fiat récents. Les devis déjà émis ne changent pas.
- Pour le mode DEX, découvre d'abord un pool, puis envoie price_mode: dex et dex_pair_address en omettant price_usd. Une tâche partagée en arrière-plan actualise les pools sélectionnés chaque minute. Des contrôles échoués ou des prix de plus de cinq minutes retirent ce token des nouveaux devis ; aucun repli silencieux vers un prix fixe ou un symbole.
- Seuls les tokens ERC-20 standard et SPL classiques sont acceptés. Token-2022/extensions et les blockchains uniquement natives sont refusés. La vérification technique n'est pas un audit de sécurité de l'émetteur/contrat ; les tokens avec frais de transfert, rebasage ou liste noire peuvent être incompatibles.
- Utilise un délai client d'au moins 60 secondes. La vérification est bornée et peut essayer des nœuds de secours. Des données invalides renvoient 400 ; des contrôles blockchain/contrat échoués 422 ; des conflits d'identité ou limites 409.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_MERCHANT_API_TOKEN |
| Content-Type | obligatoire | application/json |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| project_id | path UUID | Projet attribué à cet identifiant autorisé en écriture. |
Enregistrement de token personnalisé
| Champ | Type | Présence | Description |
|---|---|---|---|
| chain_slug | string | obligatoire | ethereum, base, bnb-chain, hyperliquid, avalanche, polygon, arbitrum, optimism ou solana. Fixe pour ce contrat. |
| contract_address | string | obligatoire | Contrat ERC-20 (0x suivi de 40 caractères hexadécimaux) ou mint SPL classique. Les nœuds vérifient l'identité réseau et les décimales exactes ; les décimales et URL RPC fournies par l'appelant sont refusées. |
| name / symbol | string / string | obligatoire | Nom d'affichage (1–80 caractères) et symbole (1–16 lettres/chiffres/points/traits de soulignement/tirets, premier caractère alphanumérique). Les identités existantes ne peuvent pas être renommées par cet endpoint. |
| price_mode | fixed | dex | facultatif | fixed par défaut pour la rétrocompatibilité. DEX utilise un pool précis découvert pour la blockchain et le contrat exacts. |
| price_usd | decimal string | mode fixed | Valeur USD fixe d'UN token, positive, 30 décimales maximum, maximum 1000000000000000000000000. Aucun exposant ni float. À omettre en mode dex. |
| dex_pair_address | string | mode dex | Adresse de pool issue de payment-token-dex-pools. Obligatoire en mode dex ; à omettre en mode fixed. Le serveur revérifie l'identité du pool, le prix, la liquidité et l'activité à chaque enregistrement. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-assets/custom" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Content-Type: application/json' \
--data-raw '{
"chain_slug": "ethereum",
"contract_address": "YOUR_VERIFIED_TOKEN_CONTRACT",
"name": "Example token",
"symbol": "EXAMPLE",
"price_usd": "0.25"
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const body = `{
"chain_slug": "ethereum",
"contract_address": "YOUR_VERIFIED_TOKEN_CONTRACT",
"name": "Example token",
"symbol": "EXAMPLE",
"price_usd": "0.25"
}`;
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-assets/custom", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$body = <<<'JSON'
{
"chain_slug": "ethereum",
"contract_address": "YOUR_VERIFIED_TOKEN_CONTRACT",
"name": "Example token",
"symbol": "EXAMPLE",
"price_usd": "0.25"
}
JSON;
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-assets/custom");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"chain_slug": "ethereum",
"contract_address": "YOUR_VERIFIED_TOKEN_CONTRACT",
"name": "Example token",
"symbol": "EXAMPLE",
"price_usd": "0.25"
}""".encode("utf-8")
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/payment-token-assets/custom",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{"data":{"asset_id":"44444444-4444-4444-8444-444444444444"}}GETLister les moyens de paiement du magasin/v1/projects/{project_id}/stores/{store_id}/payment-assetsLecture seule
Liste les actifs on-chain dans data et la disponibilité Lightning séparée dans lightning. Les moyens on-chain nécessitent des portefeuilles de blockchain prêts. Lightning utilise la connexion externe de réception vérifiée sélectionnée dans le magasin, indépendamment du portefeuille Bitcoin on-chain.
- selected est la configuration on-chain ; wallet_readiness détermine son admissibilité actuelle.
- Le membre lightning de la réponse contient payment_rail: lightning, symbol: BTC, asset_decimals: 11, enabled et ready. Il ne contient jamais d'identifiants de nœud. Configure ce moyen dans la console du magasin ; modifier le tableau assets ne change pas Lightning.
- confirmation_policy s'applique uniquement aux moyens on-chain. Lightning est réglé sans confirmations de bloc et exige le montant BOLT11 complet, sans tolérance de paiement partiel.
- Les moyens natifs et tokens d'une blockchain utilisent la même destination de facture pour le portefeuille de cette blockchain.
- Les résumés de portefeuille intégrés concernent uniquement la disponibilité et laissent les soldes vides ; utilise la route dédiée aux portefeuilles de projet pour les valeurs actuelles.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_MERCHANT_API_TOKEN |
| Accept | recommandé | application/json |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| project_id | path UUID | Projet attribué à l'identifiant ; il peut être en pause. |
| store_id | path UUID | Magasin appartenant à project_id ; il peut être en pause. |
PaymentAsset
| Champ | Type | Présence | Description |
|---|---|---|---|
| id | UUID | toujours | Identifiant persistant d'actif de paiement utilisé par les routes de politique de projet et magasin. |
| asset_key | string | toujours | Identité canonique de l'actif natif ou du contrat au format CAIP. |
| chain_slug / network | string | toujours | Identifiant de blockchain Wholly Crypto et réseau configuré. |
| caip_network_id / caip_asset_id | string / string|null | toujours | Identités canoniques de réseau et d'actif. |
| asset_kind | native | token | toujours | Indique si le règlement utilise la monnaie de la blockchain ou un contrat/mint vérifié. |
| payment_rail | string | toujours | Canal d'exécution : utxo, evm-native, solana-native, account-native, privacy-native ou token-transfer. |
| symbol / name / decimals | string / string / integer | toujours | Identité d'affichage et précision exacte en unités atomiques. |
| contract_address | string | null | toujours | Contrat ERC-20 ou mint SPL canonique pour les tokens ; null pour les actifs natifs. |
| coingecko_id | string | null | toujours | Identité de découverte/prix. Null pour les contrats personnalisés ; ne déduis jamais un prix de marché de leur symbole. Les métadonnées CoinGecko seules ne rendent jamais un token sélectionnable. |
| custom_token | boolean | toujours | Contrat personnalisé vérifié on-chain, avec prix fixe en USD ou pool DEX sélectionné au niveau du projet. |
| icon_path | path | null | toujours | Icône du token en cache local, si disponible. |
| token_standard | erc20 | spl-token | null | toujours | Standard de token vérifié à l'exécution ; null pour les actifs natifs. |
| metadata_verified_at | timestamp | null | toujours | Heure de vérification des métadonnées on-chain pour les tokens promus. |
| payment_supported / scanner_ready / balance_ready | boolean | toujours | Conditions du registre à la compilation. scanner_ready signifie que le scanner de paiement est installé ; la confirmation exige le nombre configuré de fournisseurs opérationnels au rôle exact (2 par défaut, 1 en option) ; l'indisponibilité temporaire du scanner ne bloque pas la création de factures depuis 6.0.6. balance_ready vaut true uniquement pour les adaptateurs de solde implémentés. |
| default_finality_mode | confirmations | finalized | toujours | Modèle de finalité par défaut hérité par une nouvelle politique de projet. |
| default_required_confirmations / default_monitoring_minutes | integer | toujours | Politique de confirmation et de surveillance par défaut. |
StorePaymentAsset
| Champ | Type | Présence | Description |
|---|---|---|---|
| asset | PaymentAsset | toujours | Actif natif ou token vérifié visible dans le projet. |
| project_policy | ProjectAssetPolicy | null | toujours | Politique du projet parent. |
| selected | boolean | toujours | Indique si ce moyen fait partie de la configuration souhaitée enregistrée du magasin. Il est proposé lorsque la politique du projet, le portefeuille, l'adaptateur installé et les prix sont valides. Les pannes temporaires de scanner ne le retirent pas des nouvelles factures. |
| display_order | integer | null | toujours | Ordre dans le paiement du magasin lorsqu'il est sélectionné. |
| confirmation_policy | StoreConfirmationPolicy | null | toujours | Politique effective du magasin pour un actif configuré dans le projet. Null si aucune politique de projet n'existe. |
| wallet | WalletSummary | null | toujours | Portefeuille de blockchain partagé par les actifs natifs et tokens. |
| wallet_readiness | readiness enum | toujours | État du portefeuille/de la politique uniquement ; utilise receive_readiness pour les prérequis des scanners. |
| receive_readiness | ReceiveReadiness | null | 5.5.0+ | Configuration de réception partagée et acceptation du magasin. Utilise des observations en cache ; ni réservation ni garantie. La création revérifie les exigences et le taux réel de la facture. |
StoreConfirmationPolicy
| Champ | Type | Présence | Description |
|---|---|---|---|
| finality_mode | confirmations | finalized | toujours | Indique si le règlement utilise un nombre de blocs configurable ou la finalité réseau. |
| project_required_confirmations | integer | toujours | Valeur actuelle par défaut du projet utilisée par les futures factures sans dérogation du magasin. |
| override_required_confirmations | integer | null | toujours | Nombre propre au magasin, ou null pour hériter de la valeur par défaut du projet. |
| effective_required_confirmations | integer | toujours | Nombre qui sera enregistré dans les nouvelles factures pour ce magasin et cet actif. |
| editable | boolean | toujours | False pour les réseaux finalized dont la politique de finalité ne peut pas être remplacée. |
| minimum_required_confirmations | integer | toujours | Borne inférieure incluse propre à la blockchain ; 0 n'est exposé que sur les canaux permettant l'acceptation à la détection. |
| maximum_required_confirmations | integer | toujours | Borne supérieure incluse propre à la blockchain. |
WalletSummary
| Champ | Type | Présence | Description |
|---|---|---|---|
| id / project_id / native_asset_id | UUID | toujours | Identifiants du portefeuille, du projet propriétaire et de l'actif natif de la blockchain. |
| chain_slug / network | string | toujours | Blockchain et réseau du portefeuille. |
| asset_symbol / asset_name | string | toujours | Identité d'affichage de l'actif natif de la blockchain. |
| status | pending | active | disabled | error | toujours | État opérationnel du portefeuille. |
| label | string | toujours | Libellé de l'opérateur. |
| public_key / primary_address | string | null | toujours | Identité publique du portefeuille ; aucune phrase de récupération ni clé privée n'est exposée. |
| derivation_scheme / address_format | string | null | toujours | Politique et format des adresses. |
| backup_confirmed_at | timestamp | null | toujours | Non null après confirmation de la sauvegarde de récupération par l'opérateur. |
| activation_required / activation_verified_at | boolean / timestamp|null | toujours | Les comptes partagés XRP et Stellar restent indisponibles jusqu'à ce que l'opérateur alimente l'adresse affichée et que les fournisseurs de détection configurés vérifient ce compte exact. La preuve persistante n'expire pas ; l'état actuel des scanners est contrôlé séparément pour vérifier les paiements, pas pour créer des factures. |
| receive_readiness | ReceiveReadiness | null | 5.5.0+ | Inclus dans les listes de portefeuilles : configuration de réception du projet et prérequis des scanners de blockchain. Distinct des soldes, du gas des tokens et de la disponibilité d'envoi. D'autres réponses de portefeuille peuvent le laisser null. |
| monero_wallet_rpc | MoneroWalletRpcBinding | null | toujours | État de liaison wallet-RPC externe en consultation seule pour Monero, épuré des données sensibles. Inclut endpoint, mode d'authentification, adresse principale du compte 0, indicateurs/hauteurs de preuves techniques et horodatages des attestations de l'opérateur ; les identifiants, clés et fichiers de portefeuille ne sont jamais sérialisés. |
| last_secret_revealed_at / secret_reveal_count | timestamp|null / integer | toujours | Métadonnées d'audit de divulgation des secrets côté console. |
| next_receive_index | integer | toujours | Indice de la prochaine adresse enfant réservée. |
| last_scanned_height / last_scanned_at / last_error | integer|null / timestamp|null / string|null | toujours | État du scanner de portefeuille. |
| balances | WalletAssetBalance[] | toujours | Soldes en cache pour chacun des 30 canaux natifs, plus les actifs ERC-20 et SPL vérifiés. Un wallet-RPC externe configuré en consultation seule est nécessaire pour Monero. |
| total_value_usd | decimal string | null | toujours | Somme indicative des soldes avec un prix USD actuel. |
| balance_status | pending | refreshing | fresh | stale | error | unknown | toujours | Fraîcheur agrégée du cache ; unknown est une valeur de repli prudente et aucun de ces états ne prouve le règlement d'une facture. |
| balance_checked_at | timestamp | null | toujours | Plus ancien contrôle de solde réussi pertinent représenté par l'agrégat. |
| recent_payments | WalletRecentPayment[] | toujours | Jusqu'aux trois observations valides les plus récentes detected, confirming ou final attribuées à ce portefeuille exact. |
| created_at / updated_at | RFC 3339 timestamp | toujours | Heure de création et de dernière mise à jour du portefeuille. |
ReceiveReadiness
| Champ | Type | Présence | Description |
|---|---|---|---|
| ready | boolean | toujours | Les contrôles de configuration de réception réussissent. Ne décrit pas la disponibilité de dépense, le gas, l'actualisation des soldes ni un devis futur garanti. |
| invoice_creatable | boolean | 6.0.6+ | La configuration permet un moyen de facture malgré des avertissements temporaires du scanner. Le prix de devise est vérifié à la création. Ce n'est pas une vérification de paiement : ready peut être false alors que invoice_creatable est true. Les portefeuilles manquants, politiques désactivées et adaptateurs non pris en charge restent bloquants par sécurité. |
| checked_at | timestamp | toujours | Heure d'évaluation. Une liste ne lance aucune requête réseau et n'alloue aucune adresse. |
| issues | PaymentMethodIssue[] | toujours | Vide si prêt ; sinon, avertissement de réception ou blocage de configuration. Vérifie invoice_creatable pour distinguer les avertissements temporaires du scanner des échecs de configuration de facture. |
PaymentMethodIssue
| Champ | Type | Présence | Description |
|---|---|---|---|
| chain_slug / asset_id / asset_ticker | string / UUID / string | si connu | Identifie la blockchain et l'actif concernés. Lightning peut omettre asset_id. |
| reason_code | string | toujours | scanner_provider_quorum, scanner_not_checked, scanner_unavailable, wallet_missing, wallet_disabled, wallet_backup_required, wallet_key_unavailable, wallet_activation_required, monero_binding_unavailable, rate_unavailable, custom_rate_unavailable, lightning_unavailable, project_disabled, store_disabled, chain_disabled, asset_disabled ou asset_not_accepted. |
| message / action | string | si disponible | Explication pour le commerçant et identifiant d'action : chain_connections, wallets, rates, payment_methods, project_settings ou store_settings. Aucun identifiant ni URL privée de fournisseur. |
| required_endpoint_role | string | null | on-chain | Rôle API de scanner préféré (ancien champ). Utilise accepted_endpoint_roles pour la liste complète de compatibilité. L'état de base du nœud ne prouve pas la prise en charge de l'historique des paiements. |
| accepted_endpoint_roles | string[] | null | on-chain | Dialectes API compatibles, pas une preuve d'historique ou de capacité d'endpoint. node-rpc brut prend en charge BTC/BCH/LTC/DOGE/DASH et ZEC transparent (blocs décodés complets, 1–48 confirmations), TRX natif solidifié, ALGO natif via algod, XTZ via Octez, DOT Asset Hub finalisé via métadonnées SCALE et XLM natif via Stellar RPC avec identifiant de mémo de facture. Un historique élagué ou incomplet n'est pas admissible. Ces adaptateurs bruts n'ajoutent pas de canaux de tokens. Les API indexées restent des alternatives ; voir le tableau ci-dessous. Les sources brutes/indexées mixtes vérifient indépendamment des fenêtres limitées ; deux fournisseurs indépendants restent le défaut, pas des alias d'un même opérateur. La hauteur de base d'un nœud, les informations de blockchain ORDnet et un relais EVM pour un canal non EVM ne sont pas des preuves de réception. Monero nécessite toujours un wallet-RPC en consultation seule lié au projet. |
| healthy_endpoints | integer | on-chain | Endpoints correspondants opérationnels, pas le nombre de fournisseurs indépendants. |
| usable_independent_providers / required_independent_providers | integer | on-chain | Emplacements de vérification utilisables, limités à deux. required_independent_providers est le réglage de blockchain : 2 par défaut, ou 1 après choix explicite de l'administrateur. Le mode à deux fournisseurs exige des clés fournisseur ET des hôtes différents. Les sources désactivées, périmées (plus de dix minutes) ou en pause ne remplissent pas d'emplacement. Lightning utilise ses propres règles de connexion. |
| last_checked_at | timestamp | null | on-chain | Dernier contrôle d'état de l'endpoint correspondant, distinct de l'heure d'évaluation. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"data": [
{ "asset": { "id": "ASSET_UUID", "chain_slug": "ethereum", "symbol": "USDC", "asset_kind": "token", "token_standard": "erc20", "scanner_ready": true }, "project_policy": { "enabled": true, "required_confirmations": 12 }, "selected": true, "display_order": 0, "confirmation_policy": { "finality_mode": "confirmations", "project_required_confirmations": 12, "override_required_confirmations": 3, "effective_required_confirmations": 3, "editable": true, "minimum_required_confirmations": 0, "maximum_required_confirmations": 48 }, "wallet": { "id": "WALLET_UUID", "status": "active" }, "wallet_readiness": "ready" }
],
"lightning": { "payment_rail": "lightning", "symbol": "BTC", "asset_decimals": 11, "enabled": true, "ready": true }
}PUTRemplacer les moyens de paiement du magasin/v1/projects/{project_id}/stores/{store_id}/payment-assetsLecture + écriture
Remplace atomiquement tout le sous-ensemble ordonné d'actifs du magasin et renvoie la liste actualisée. Les actifs omis sont désélectionnés.
- Le tableau accepte au maximum 64 actifs et ordres d'affichage uniques.
- Les sélections représentent la configuration souhaitée enregistrée et peuvent être préparées avant la sauvegarde d'un portefeuille ou pendant la pause d'une blockchain. La création de factures ne propose toujours que les moyens dont la politique du projet, la politique native parente, le portefeuille et les contrôles d'exécution sont prêts.
- Envoie un tableau assets vide pour ne configurer aucun moyen de paiement.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_MERCHANT_API_TOKEN |
| Content-Type | obligatoire | application/json |
| Accept | recommandé | application/json |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| project_id | path UUID | Projet attribué à l'identifiant ; il peut être en pause. |
| store_id | path UUID | Magasin appartenant à project_id ; il peut être en pause. |
Corps de sélection des actifs de paiement du magasin
| Champ | Type | Présence | Description |
|---|---|---|---|
| assets | StoreAssetSelection[] | obligatoire | Liste de remplacement complète, 64 entrées maximum. Chaque entrée contient un asset_id unique et un display_order unique de 0 à 10 000. |
PaymentAsset
| Champ | Type | Présence | Description |
|---|---|---|---|
| id | UUID | toujours | Identifiant persistant d'actif de paiement utilisé par les routes de politique de projet et magasin. |
| asset_key | string | toujours | Identité canonique de l'actif natif ou du contrat au format CAIP. |
| chain_slug / network | string | toujours | Identifiant de blockchain Wholly Crypto et réseau configuré. |
| caip_network_id / caip_asset_id | string / string|null | toujours | Identités canoniques de réseau et d'actif. |
| asset_kind | native | token | toujours | Indique si le règlement utilise la monnaie de la blockchain ou un contrat/mint vérifié. |
| payment_rail | string | toujours | Canal d'exécution : utxo, evm-native, solana-native, account-native, privacy-native ou token-transfer. |
| symbol / name / decimals | string / string / integer | toujours | Identité d'affichage et précision exacte en unités atomiques. |
| contract_address | string | null | toujours | Contrat ERC-20 ou mint SPL canonique pour les tokens ; null pour les actifs natifs. |
| coingecko_id | string | null | toujours | Identité de découverte/prix. Null pour les contrats personnalisés ; ne déduis jamais un prix de marché de leur symbole. Les métadonnées CoinGecko seules ne rendent jamais un token sélectionnable. |
| custom_token | boolean | toujours | Contrat personnalisé vérifié on-chain, avec prix fixe en USD ou pool DEX sélectionné au niveau du projet. |
| icon_path | path | null | toujours | Icône du token en cache local, si disponible. |
| token_standard | erc20 | spl-token | null | toujours | Standard de token vérifié à l'exécution ; null pour les actifs natifs. |
| metadata_verified_at | timestamp | null | toujours | Heure de vérification des métadonnées on-chain pour les tokens promus. |
| payment_supported / scanner_ready / balance_ready | boolean | toujours | Conditions du registre à la compilation. scanner_ready signifie que le scanner de paiement est installé ; la confirmation exige le nombre configuré de fournisseurs opérationnels au rôle exact (2 par défaut, 1 en option) ; l'indisponibilité temporaire du scanner ne bloque pas la création de factures depuis 6.0.6. balance_ready vaut true uniquement pour les adaptateurs de solde implémentés. |
| default_finality_mode | confirmations | finalized | toujours | Modèle de finalité par défaut hérité par une nouvelle politique de projet. |
| default_required_confirmations / default_monitoring_minutes | integer | toujours | Politique de confirmation et de surveillance par défaut. |
StorePaymentAsset
| Champ | Type | Présence | Description |
|---|---|---|---|
| asset | PaymentAsset | toujours | Actif natif ou token vérifié visible dans le projet. |
| project_policy | ProjectAssetPolicy | null | toujours | Politique du projet parent. |
| selected | boolean | toujours | Indique si ce moyen fait partie de la configuration souhaitée enregistrée du magasin. Il est proposé lorsque la politique du projet, le portefeuille, l'adaptateur installé et les prix sont valides. Les pannes temporaires de scanner ne le retirent pas des nouvelles factures. |
| display_order | integer | null | toujours | Ordre dans le paiement du magasin lorsqu'il est sélectionné. |
| confirmation_policy | StoreConfirmationPolicy | null | toujours | Politique effective du magasin pour un actif configuré dans le projet. Null si aucune politique de projet n'existe. |
| wallet | WalletSummary | null | toujours | Portefeuille de blockchain partagé par les actifs natifs et tokens. |
| wallet_readiness | readiness enum | toujours | État du portefeuille/de la politique uniquement ; utilise receive_readiness pour les prérequis des scanners. |
| receive_readiness | ReceiveReadiness | null | 5.5.0+ | Configuration de réception partagée et acceptation du magasin. Utilise des observations en cache ; ni réservation ni garantie. La création revérifie les exigences et le taux réel de la facture. |
StoreConfirmationPolicy
| Champ | Type | Présence | Description |
|---|---|---|---|
| finality_mode | confirmations | finalized | toujours | Indique si le règlement utilise un nombre de blocs configurable ou la finalité réseau. |
| project_required_confirmations | integer | toujours | Valeur actuelle par défaut du projet utilisée par les futures factures sans dérogation du magasin. |
| override_required_confirmations | integer | null | toujours | Nombre propre au magasin, ou null pour hériter de la valeur par défaut du projet. |
| effective_required_confirmations | integer | toujours | Nombre qui sera enregistré dans les nouvelles factures pour ce magasin et cet actif. |
| editable | boolean | toujours | False pour les réseaux finalized dont la politique de finalité ne peut pas être remplacée. |
| minimum_required_confirmations | integer | toujours | Borne inférieure incluse propre à la blockchain ; 0 n'est exposé que sur les canaux permettant l'acceptation à la détection. |
| maximum_required_confirmations | integer | toujours | Borne supérieure incluse propre à la blockchain. |
ReceiveReadiness
| Champ | Type | Présence | Description |
|---|---|---|---|
| ready | boolean | toujours | Les contrôles de configuration de réception réussissent. Ne décrit pas la disponibilité de dépense, le gas, l'actualisation des soldes ni un devis futur garanti. |
| invoice_creatable | boolean | 6.0.6+ | La configuration permet un moyen de facture malgré des avertissements temporaires du scanner. Le prix de devise est vérifié à la création. Ce n'est pas une vérification de paiement : ready peut être false alors que invoice_creatable est true. Les portefeuilles manquants, politiques désactivées et adaptateurs non pris en charge restent bloquants par sécurité. |
| checked_at | timestamp | toujours | Heure d'évaluation. Une liste ne lance aucune requête réseau et n'alloue aucune adresse. |
| issues | PaymentMethodIssue[] | toujours | Vide si prêt ; sinon, avertissement de réception ou blocage de configuration. Vérifie invoice_creatable pour distinguer les avertissements temporaires du scanner des échecs de configuration de facture. |
PaymentMethodIssue
| Champ | Type | Présence | Description |
|---|---|---|---|
| chain_slug / asset_id / asset_ticker | string / UUID / string | si connu | Identifie la blockchain et l'actif concernés. Lightning peut omettre asset_id. |
| reason_code | string | toujours | scanner_provider_quorum, scanner_not_checked, scanner_unavailable, wallet_missing, wallet_disabled, wallet_backup_required, wallet_key_unavailable, wallet_activation_required, monero_binding_unavailable, rate_unavailable, custom_rate_unavailable, lightning_unavailable, project_disabled, store_disabled, chain_disabled, asset_disabled ou asset_not_accepted. |
| message / action | string | si disponible | Explication pour le commerçant et identifiant d'action : chain_connections, wallets, rates, payment_methods, project_settings ou store_settings. Aucun identifiant ni URL privée de fournisseur. |
| required_endpoint_role | string | null | on-chain | Rôle API de scanner préféré (ancien champ). Utilise accepted_endpoint_roles pour la liste complète de compatibilité. L'état de base du nœud ne prouve pas la prise en charge de l'historique des paiements. |
| accepted_endpoint_roles | string[] | null | on-chain | Dialectes API compatibles, pas une preuve d'historique ou de capacité d'endpoint. node-rpc brut prend en charge BTC/BCH/LTC/DOGE/DASH et ZEC transparent (blocs décodés complets, 1–48 confirmations), TRX natif solidifié, ALGO natif via algod, XTZ via Octez, DOT Asset Hub finalisé via métadonnées SCALE et XLM natif via Stellar RPC avec identifiant de mémo de facture. Un historique élagué ou incomplet n'est pas admissible. Ces adaptateurs bruts n'ajoutent pas de canaux de tokens. Les API indexées restent des alternatives ; voir le tableau ci-dessous. Les sources brutes/indexées mixtes vérifient indépendamment des fenêtres limitées ; deux fournisseurs indépendants restent le défaut, pas des alias d'un même opérateur. La hauteur de base d'un nœud, les informations de blockchain ORDnet et un relais EVM pour un canal non EVM ne sont pas des preuves de réception. Monero nécessite toujours un wallet-RPC en consultation seule lié au projet. |
| healthy_endpoints | integer | on-chain | Endpoints correspondants opérationnels, pas le nombre de fournisseurs indépendants. |
| usable_independent_providers / required_independent_providers | integer | on-chain | Emplacements de vérification utilisables, limités à deux. required_independent_providers est le réglage de blockchain : 2 par défaut, ou 1 après choix explicite de l'administrateur. Le mode à deux fournisseurs exige des clés fournisseur ET des hôtes différents. Les sources désactivées, périmées (plus de dix minutes) ou en pause ne remplissent pas d'emplacement. Lightning utilise ses propres règles de connexion. |
| last_checked_at | timestamp | null | on-chain | Dernier contrôle d'état de l'endpoint correspondant, distinct de l'heure d'évaluation. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request PUT \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Content-Type: application/json' \
--data-raw '{
"assets": [
{
"asset_id": "YOUR_ASSET_ID",
"display_order": 0
}
]
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const body = `{
"assets": [
{
"asset_id": "YOUR_ASSET_ID",
"display_order": 0
}
]
}`;
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets", {
method: "PUT",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$body = <<<'JSON'
{
"assets": [
{
"asset_id": "YOUR_ASSET_ID",
"display_order": 0
}
]
}
JSON;
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"assets": [
{
"asset_id": "YOUR_ASSET_ID",
"display_order": 0
}
]
}""".encode("utf-8")
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets",
method="PUT", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"data": [
{ "asset": { "id": "44444444-4444-4444-8444-444444444444", "symbol": "USDC" }, "selected": true, "display_order": 0, "confirmation_policy": { "finality_mode": "confirmations", "project_required_confirmations": 12, "override_required_confirmations": null, "effective_required_confirmations": 12, "editable": true, "minimum_required_confirmations": 0, "maximum_required_confirmations": 48 }, "wallet_readiness": "ready" }
]
}PUTDéfinir une politique de confirmation du magasin/v1/projects/{project_id}/stores/{store_id}/payment-assets/{asset_id}/confirmation-policyLecture + écriture
Définit ou efface une dérogation de confirmations propre au magasin et renvoie la liste actualisée des moyens de paiement. L'actif doit déjà être sélectionné pour le magasin. La configuration reste disponible quand le projet, magasin, la blockchain ou le portefeuille est en pause.
- Utilise {"strategy":"inherit"} pour supprimer la dérogation du magasin et suivre la valeur actuelle par défaut du projet pour les futures factures.
- Les réseaux finalized renvoient editable false et utilisent la finalité réseau ; ils n'acceptent pas de dérogation personnalisée au nombre de blocs.
- La valeur 0 signifie accepter à la détection sans confirmation réseau ni protection contre les réorganisations. Elle n'est acceptée que si minimum_required_confirmations vaut 0.
- Les changements de politique affectent uniquement les nouvelles factures. Les factures existantes conservent l'instantané de la politique de confirmation projet/magasin capturé à la création.
- Les mises à jour portent sur un actif à la fois ; sérialise les modifications simultanées du même actif du magasin et utilise la réponse actualisée comme état courant.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_MERCHANT_API_TOKEN |
| Content-Type | obligatoire | application/json |
| Accept | recommandé | application/json |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| project_id | path UUID | Projet attribué à l'identifiant ; il peut être en pause. |
| store_id | path UUID | Magasin appartenant à project_id ; il peut être en pause. |
| asset_id | path UUID | Actif de paiement actuellement sélectionné dans le magasin à mettre à jour. |
Corps de politique de confirmation du magasin
| Champ | Type | Présence | Description |
|---|---|---|---|
| strategy | inherit | custom | obligatoire | Stratégie étiquetée. inherit supprime la dérogation du magasin ; custom nécessite required_confirmations. |
| required_confirmations | integer | custom uniquement | Entier dans le minimum/maximum renvoyé pour cet actif. Les champs inconnus ou supplémentaires sont refusés. |
PaymentAsset
| Champ | Type | Présence | Description |
|---|---|---|---|
| id | UUID | toujours | Identifiant persistant d'actif de paiement utilisé par les routes de politique de projet et magasin. |
| asset_key | string | toujours | Identité canonique de l'actif natif ou du contrat au format CAIP. |
| chain_slug / network | string | toujours | Identifiant de blockchain Wholly Crypto et réseau configuré. |
| caip_network_id / caip_asset_id | string / string|null | toujours | Identités canoniques de réseau et d'actif. |
| asset_kind | native | token | toujours | Indique si le règlement utilise la monnaie de la blockchain ou un contrat/mint vérifié. |
| payment_rail | string | toujours | Canal d'exécution : utxo, evm-native, solana-native, account-native, privacy-native ou token-transfer. |
| symbol / name / decimals | string / string / integer | toujours | Identité d'affichage et précision exacte en unités atomiques. |
| contract_address | string | null | toujours | Contrat ERC-20 ou mint SPL canonique pour les tokens ; null pour les actifs natifs. |
| coingecko_id | string | null | toujours | Identité de découverte/prix. Null pour les contrats personnalisés ; ne déduis jamais un prix de marché de leur symbole. Les métadonnées CoinGecko seules ne rendent jamais un token sélectionnable. |
| custom_token | boolean | toujours | Contrat personnalisé vérifié on-chain, avec prix fixe en USD ou pool DEX sélectionné au niveau du projet. |
| icon_path | path | null | toujours | Icône du token en cache local, si disponible. |
| token_standard | erc20 | spl-token | null | toujours | Standard de token vérifié à l'exécution ; null pour les actifs natifs. |
| metadata_verified_at | timestamp | null | toujours | Heure de vérification des métadonnées on-chain pour les tokens promus. |
| payment_supported / scanner_ready / balance_ready | boolean | toujours | Conditions du registre à la compilation. scanner_ready signifie que le scanner de paiement est installé ; la confirmation exige le nombre configuré de fournisseurs opérationnels au rôle exact (2 par défaut, 1 en option) ; l'indisponibilité temporaire du scanner ne bloque pas la création de factures depuis 6.0.6. balance_ready vaut true uniquement pour les adaptateurs de solde implémentés. |
| default_finality_mode | confirmations | finalized | toujours | Modèle de finalité par défaut hérité par une nouvelle politique de projet. |
| default_required_confirmations / default_monitoring_minutes | integer | toujours | Politique de confirmation et de surveillance par défaut. |
StorePaymentAsset
| Champ | Type | Présence | Description |
|---|---|---|---|
| asset | PaymentAsset | toujours | Actif natif ou token vérifié visible dans le projet. |
| project_policy | ProjectAssetPolicy | null | toujours | Politique du projet parent. |
| selected | boolean | toujours | Indique si ce moyen fait partie de la configuration souhaitée enregistrée du magasin. Il est proposé lorsque la politique du projet, le portefeuille, l'adaptateur installé et les prix sont valides. Les pannes temporaires de scanner ne le retirent pas des nouvelles factures. |
| display_order | integer | null | toujours | Ordre dans le paiement du magasin lorsqu'il est sélectionné. |
| confirmation_policy | StoreConfirmationPolicy | null | toujours | Politique effective du magasin pour un actif configuré dans le projet. Null si aucune politique de projet n'existe. |
| wallet | WalletSummary | null | toujours | Portefeuille de blockchain partagé par les actifs natifs et tokens. |
| wallet_readiness | readiness enum | toujours | État du portefeuille/de la politique uniquement ; utilise receive_readiness pour les prérequis des scanners. |
| receive_readiness | ReceiveReadiness | null | 5.5.0+ | Configuration de réception partagée et acceptation du magasin. Utilise des observations en cache ; ni réservation ni garantie. La création revérifie les exigences et le taux réel de la facture. |
StoreConfirmationPolicy
| Champ | Type | Présence | Description |
|---|---|---|---|
| finality_mode | confirmations | finalized | toujours | Indique si le règlement utilise un nombre de blocs configurable ou la finalité réseau. |
| project_required_confirmations | integer | toujours | Valeur actuelle par défaut du projet utilisée par les futures factures sans dérogation du magasin. |
| override_required_confirmations | integer | null | toujours | Nombre propre au magasin, ou null pour hériter de la valeur par défaut du projet. |
| effective_required_confirmations | integer | toujours | Nombre qui sera enregistré dans les nouvelles factures pour ce magasin et cet actif. |
| editable | boolean | toujours | False pour les réseaux finalized dont la politique de finalité ne peut pas être remplacée. |
| minimum_required_confirmations | integer | toujours | Borne inférieure incluse propre à la blockchain ; 0 n'est exposé que sur les canaux permettant l'acceptation à la détection. |
| maximum_required_confirmations | integer | toujours | Borne supérieure incluse propre à la blockchain. |
ReceiveReadiness
| Champ | Type | Présence | Description |
|---|---|---|---|
| ready | boolean | toujours | Les contrôles de configuration de réception réussissent. Ne décrit pas la disponibilité de dépense, le gas, l'actualisation des soldes ni un devis futur garanti. |
| invoice_creatable | boolean | 6.0.6+ | La configuration permet un moyen de facture malgré des avertissements temporaires du scanner. Le prix de devise est vérifié à la création. Ce n'est pas une vérification de paiement : ready peut être false alors que invoice_creatable est true. Les portefeuilles manquants, politiques désactivées et adaptateurs non pris en charge restent bloquants par sécurité. |
| checked_at | timestamp | toujours | Heure d'évaluation. Une liste ne lance aucune requête réseau et n'alloue aucune adresse. |
| issues | PaymentMethodIssue[] | toujours | Vide si prêt ; sinon, avertissement de réception ou blocage de configuration. Vérifie invoice_creatable pour distinguer les avertissements temporaires du scanner des échecs de configuration de facture. |
PaymentMethodIssue
| Champ | Type | Présence | Description |
|---|---|---|---|
| chain_slug / asset_id / asset_ticker | string / UUID / string | si connu | Identifie la blockchain et l'actif concernés. Lightning peut omettre asset_id. |
| reason_code | string | toujours | scanner_provider_quorum, scanner_not_checked, scanner_unavailable, wallet_missing, wallet_disabled, wallet_backup_required, wallet_key_unavailable, wallet_activation_required, monero_binding_unavailable, rate_unavailable, custom_rate_unavailable, lightning_unavailable, project_disabled, store_disabled, chain_disabled, asset_disabled ou asset_not_accepted. |
| message / action | string | si disponible | Explication pour le commerçant et identifiant d'action : chain_connections, wallets, rates, payment_methods, project_settings ou store_settings. Aucun identifiant ni URL privée de fournisseur. |
| required_endpoint_role | string | null | on-chain | Rôle API de scanner préféré (ancien champ). Utilise accepted_endpoint_roles pour la liste complète de compatibilité. L'état de base du nœud ne prouve pas la prise en charge de l'historique des paiements. |
| accepted_endpoint_roles | string[] | null | on-chain | Dialectes API compatibles, pas une preuve d'historique ou de capacité d'endpoint. node-rpc brut prend en charge BTC/BCH/LTC/DOGE/DASH et ZEC transparent (blocs décodés complets, 1–48 confirmations), TRX natif solidifié, ALGO natif via algod, XTZ via Octez, DOT Asset Hub finalisé via métadonnées SCALE et XLM natif via Stellar RPC avec identifiant de mémo de facture. Un historique élagué ou incomplet n'est pas admissible. Ces adaptateurs bruts n'ajoutent pas de canaux de tokens. Les API indexées restent des alternatives ; voir le tableau ci-dessous. Les sources brutes/indexées mixtes vérifient indépendamment des fenêtres limitées ; deux fournisseurs indépendants restent le défaut, pas des alias d'un même opérateur. La hauteur de base d'un nœud, les informations de blockchain ORDnet et un relais EVM pour un canal non EVM ne sont pas des preuves de réception. Monero nécessite toujours un wallet-RPC en consultation seule lié au projet. |
| healthy_endpoints | integer | on-chain | Endpoints correspondants opérationnels, pas le nombre de fournisseurs indépendants. |
| usable_independent_providers / required_independent_providers | integer | on-chain | Emplacements de vérification utilisables, limités à deux. required_independent_providers est le réglage de blockchain : 2 par défaut, ou 1 après choix explicite de l'administrateur. Le mode à deux fournisseurs exige des clés fournisseur ET des hôtes différents. Les sources désactivées, périmées (plus de dix minutes) ou en pause ne remplissent pas d'emplacement. Lightning utilise ses propres règles de connexion. |
| last_checked_at | timestamp | null | on-chain | Dernier contrôle d'état de l'endpoint correspondant, distinct de l'heure d'évaluation. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request PUT \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets/YOUR_ASSET_ID/confirmation-policy" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Content-Type: application/json' \
--data-raw '{
"strategy": "custom",
"required_confirmations": 0
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const body = `{
"strategy": "custom",
"required_confirmations": 0
}`;
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets/YOUR_ASSET_ID/confirmation-policy", {
method: "PUT",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$body = <<<'JSON'
{
"strategy": "custom",
"required_confirmations": 0
}
JSON;
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets/YOUR_ASSET_ID/confirmation-policy");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"strategy": "custom",
"required_confirmations": 0
}""".encode("utf-8")
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/payment-assets/YOUR_ASSET_ID/confirmation-policy",
method="PUT", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"data": [
{
"asset": { "id": "YOUR_ASSET_ID", "chain_slug": "bitcoin", "symbol": "BTC" },
"selected": true,
"display_order": 0,
"confirmation_policy": {
"finality_mode": "confirmations",
"project_required_confirmations": 2,
"override_required_confirmations": 0,
"effective_required_confirmations": 0,
"editable": true,
"minimum_required_confirmations": 0,
"maximum_required_confirmations": 10000
},
"wallet_readiness": "ready"
}
]
}GETLister les portefeuilles et soldes du projet/v1/projects/{project_id}/walletsLecture seule
Renvoie les métadonnées publiques des portefeuilles et chaque actif enregistré pouvant lire un solde sur la blockchain et le réseau exacts du portefeuille. Les 30 canaux natifs sont couverts ; les actifs ERC-20 et SPL vérifiés sont aussi suivis. Les actifs apparaissent immédiatement, même avant leur première analyse ou s'ils ne sont pas acceptés pour les paiements. project_enabled indique l'acceptation de paiement ; tracking_active indique indépendamment l'éligibilité à l'actualisation en lecture seule. Monero nécessite son wallet-RPC externe en consultation seule lié au projet. Analyser les soldes dans la console donne priorité à des lectures limitées avec progression/erreurs par actif ; seuls les cycles complets actualisent les totaux récents. Le règlement des factures reste fondé sur la surveillance des transactions et la politique de confirmation, pas sur ces soldes en cache.
- Cette route bearer ne renvoie jamais de phrase de récupération, clé privée, secret chiffré ni méthode de dépense.
- Un actif nouvellement enregistré sur la même blockchain est renvoyé avec des soldes null et le statut pending avant sa première analyse complète ; jamais avec un zéro inventé.
- Désactiver un projet, un portefeuille pour l'acceptation de paiement, un canal natif ou un actif n'arrête pas le suivi des soldes en lecture seule : les portefeuilles actifs et désactivés avec adresse principale continuent d'actualiser chaque actif enregistré et pris en charge sur la même blockchain. Les portefeuilles pending et error ne sont pas analysés.
- project_enabled indique uniquement la politique d'acceptation des actifs du projet et peut être false alors que tracking_active reste true.
- balance et balance_atomic sont des chaînes exactes ; price_usd, value_usd et total_value_usd sont indicatifs et peuvent être null. Un statut de solde récent ne garantit pas un prix de marché récent.
- La valorisation privilégie les prix CoinGecko de deux heures maximum. Les monnaies natives et USDC/USDT canoniques vérifiés peuvent utiliser en secours les cours USD Kraken/Binance activés de cinq minutes maximum, fournisseur principal d'abord. Aucune parité dollar supposée ni tarification de token personnalisé par symbole seul ; les prix fixes/DEX du projet restent distincts. Les devis de facture ne changent pas.
- Pending n'a pas d'instantané complet. Refreshing conserve le dernier montant complet et checked_at ; cela ne signifie pas qu'un transfert blockchain est en attente. Les montants stale/error peuvent aussi conserver d'anciennes valeurs. Ne traite jamais un cache indisponible comme zéro ni comme un paiement manquant. Les actualisations courantes EVM/Solana réutilisent les adresses vides récemment contrôlées jusqu'à 30 minutes entre audits, tandis que les adresses approvisionnées, nouvelles ou modifiées sont revérifiées. La commande explicite Analyser les soldes de la console demande une analyse complète.
- recent_payments est limité à trois observations par portefeuille et exclut l'historique invalidé.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_MERCHANT_API_TOKEN |
| Accept | recommandé | application/json |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| project_id | path UUID | Projet activé attribué à l'identifiant. |
WalletSummary
| Champ | Type | Présence | Description |
|---|---|---|---|
| id / project_id / native_asset_id | UUID | toujours | Identifiants du portefeuille, du projet propriétaire et de l'actif natif de la blockchain. |
| chain_slug / network | string | toujours | Blockchain et réseau du portefeuille. |
| asset_symbol / asset_name | string | toujours | Identité d'affichage de l'actif natif de la blockchain. |
| status | pending | active | disabled | error | toujours | État opérationnel du portefeuille. |
| label | string | toujours | Libellé de l'opérateur. |
| public_key / primary_address | string | null | toujours | Identité publique du portefeuille ; aucune phrase de récupération ni clé privée n'est exposée. |
| derivation_scheme / address_format | string | null | toujours | Politique et format des adresses. |
| backup_confirmed_at | timestamp | null | toujours | Non null après confirmation de la sauvegarde de récupération par l'opérateur. |
| activation_required / activation_verified_at | boolean / timestamp|null | toujours | Les comptes partagés XRP et Stellar restent indisponibles jusqu'à ce que l'opérateur alimente l'adresse affichée et que les fournisseurs de détection configurés vérifient ce compte exact. La preuve persistante n'expire pas ; l'état actuel des scanners est contrôlé séparément pour vérifier les paiements, pas pour créer des factures. |
| receive_readiness | ReceiveReadiness | null | 5.5.0+ | Inclus dans les listes de portefeuilles : configuration de réception du projet et prérequis des scanners de blockchain. Distinct des soldes, du gas des tokens et de la disponibilité d'envoi. D'autres réponses de portefeuille peuvent le laisser null. |
| monero_wallet_rpc | MoneroWalletRpcBinding | null | toujours | État de liaison wallet-RPC externe en consultation seule pour Monero, épuré des données sensibles. Inclut endpoint, mode d'authentification, adresse principale du compte 0, indicateurs/hauteurs de preuves techniques et horodatages des attestations de l'opérateur ; les identifiants, clés et fichiers de portefeuille ne sont jamais sérialisés. |
| last_secret_revealed_at / secret_reveal_count | timestamp|null / integer | toujours | Métadonnées d'audit de divulgation des secrets côté console. |
| next_receive_index | integer | toujours | Indice de la prochaine adresse enfant réservée. |
| last_scanned_height / last_scanned_at / last_error | integer|null / timestamp|null / string|null | toujours | État du scanner de portefeuille. |
| balances | WalletAssetBalance[] | toujours | Soldes en cache pour chacun des 30 canaux natifs, plus les actifs ERC-20 et SPL vérifiés. Un wallet-RPC externe configuré en consultation seule est nécessaire pour Monero. |
| total_value_usd | decimal string | null | toujours | Somme indicative des soldes avec un prix USD actuel. |
| balance_status | pending | refreshing | fresh | stale | error | unknown | toujours | Fraîcheur agrégée du cache ; unknown est une valeur de repli prudente et aucun de ces états ne prouve le règlement d'une facture. |
| balance_checked_at | timestamp | null | toujours | Plus ancien contrôle de solde réussi pertinent représenté par l'agrégat. |
| recent_payments | WalletRecentPayment[] | toujours | Jusqu'aux trois observations valides les plus récentes detected, confirming ou final attribuées à ce portefeuille exact. |
| created_at / updated_at | RFC 3339 timestamp | toujours | Heure de création et de dernière mise à jour du portefeuille. |
WalletAssetBalance
| Champ | Type | Présence | Description |
|---|---|---|---|
| wallet_id / asset_id | UUID | toujours | Identités du portefeuille et de l'actif persistant. |
| project_enabled | boolean | toujours | Indique si l'actif est actuellement activé par la politique d'actifs du projet. |
| active_store_count | integer | toujours | Nombre de magasins activés sélectionnant actuellement cet actif. C'est une vue de l'acceptation ; le suivi des soldes en lecture seule reste indépendant. |
| active_store_ids | UUID[] | toujours | Magasins activés dans ce projet acceptant actuellement l'actif. Permet un filtrage local exact par magasin sans autre requête API. |
| tracking_active | boolean | toujours | Indique si ce portefeuille lisible et l'actif enregistré sur la même blockchain sont éligibles aux actualisations de solde en arrière-plan. Les interrupteurs d'acceptation du projet et des moyens de paiement ne suspendent pas le suivi en lecture seule. |
| asset_kind | native | token | toujours | Monnaie native ou actif de contrat/mint vérifié. |
| contract_address | string | null | toujours | Contrat ou mint du token ; null pour la monnaie native. |
| symbol / name / decimals | string / string / integer | toujours | Identité d'affichage et précision atomique. |
| coingecko_id | string | null | toujours | Identité de tarification lorsqu'elle est associée. |
| balance / balance_atomic | decimal string|null / integer string|null | toujours | Solde exact affiché et atomique sur l'adresse principale du portefeuille et les adresses de facture émises. Null tant qu'une valeur complète est indisponible. |
| price_usd | decimal string | null | toujours | Prix unitaire USD indicatif en cache utilisé pour la valorisation. |
| value_usd | decimal string | null | toujours | Valorisation fiat indicative lorsqu'un taux actuel existe. |
| status | pending | refreshing | fresh | stale | error | toujours | État d'analyse en cache. refreshing peut conserver un solde complet : utilise checked_at pour son âge. Pending signifie aucun instantané complet. Aucun de ces états ne prouve qu'un transfert est en attente ni qu'une facture est réglée. |
| checked_at | timestamp | null | toujours | Heure représentée par une analyse complète du solde. |
| last_error | string | null | toujours | Diagnostic sûr pour l'opérateur. |
WalletRecentPayment
| Champ | Type | Présence | Description |
|---|---|---|---|
| invoice_public_id | UUID | toujours | Identité de facture visible par le client associée à l'observation. |
| chain_slug / symbol | string | toujours | Blockchain et symbole d'affichage de la monnaie native ou du token vérifié. |
| transaction_id / event_index | string / integer | toujours | Identité canonique de transaction et d'événement de transfert. |
| amount | decimal string | toujours | Montant exact observé de l'actif sans conversion en virgule flottante. |
| status | detected | confirming | final | toujours | État actuel valide de l'observation. Les observations réorganisées, remplacées et invalides sont exclues. |
| confirmations | integer | toujours | Dernier nombre de confirmations observé. |
| observed_at | RFC 3339 timestamp | toujours | Heure à laquelle Wholly Crypto a observé le paiement pour la première fois. |
ReceiveReadiness
| Champ | Type | Présence | Description |
|---|---|---|---|
| ready | boolean | toujours | Les contrôles de configuration de réception réussissent. Ne décrit pas la disponibilité de dépense, le gas, l'actualisation des soldes ni un devis futur garanti. |
| invoice_creatable | boolean | 6.0.6+ | La configuration permet un moyen de facture malgré des avertissements temporaires du scanner. Le prix de devise est vérifié à la création. Ce n'est pas une vérification de paiement : ready peut être false alors que invoice_creatable est true. Les portefeuilles manquants, politiques désactivées et adaptateurs non pris en charge restent bloquants par sécurité. |
| checked_at | timestamp | toujours | Heure d'évaluation. Une liste ne lance aucune requête réseau et n'alloue aucune adresse. |
| issues | PaymentMethodIssue[] | toujours | Vide si prêt ; sinon, avertissement de réception ou blocage de configuration. Vérifie invoice_creatable pour distinguer les avertissements temporaires du scanner des échecs de configuration de facture. |
PaymentMethodIssue
| Champ | Type | Présence | Description |
|---|---|---|---|
| chain_slug / asset_id / asset_ticker | string / UUID / string | si connu | Identifie la blockchain et l'actif concernés. Lightning peut omettre asset_id. |
| reason_code | string | toujours | scanner_provider_quorum, scanner_not_checked, scanner_unavailable, wallet_missing, wallet_disabled, wallet_backup_required, wallet_key_unavailable, wallet_activation_required, monero_binding_unavailable, rate_unavailable, custom_rate_unavailable, lightning_unavailable, project_disabled, store_disabled, chain_disabled, asset_disabled ou asset_not_accepted. |
| message / action | string | si disponible | Explication pour le commerçant et identifiant d'action : chain_connections, wallets, rates, payment_methods, project_settings ou store_settings. Aucun identifiant ni URL privée de fournisseur. |
| required_endpoint_role | string | null | on-chain | Rôle API de scanner préféré (ancien champ). Utilise accepted_endpoint_roles pour la liste complète de compatibilité. L'état de base du nœud ne prouve pas la prise en charge de l'historique des paiements. |
| accepted_endpoint_roles | string[] | null | on-chain | Dialectes API compatibles, pas une preuve d'historique ou de capacité d'endpoint. node-rpc brut prend en charge BTC/BCH/LTC/DOGE/DASH et ZEC transparent (blocs décodés complets, 1–48 confirmations), TRX natif solidifié, ALGO natif via algod, XTZ via Octez, DOT Asset Hub finalisé via métadonnées SCALE et XLM natif via Stellar RPC avec identifiant de mémo de facture. Un historique élagué ou incomplet n'est pas admissible. Ces adaptateurs bruts n'ajoutent pas de canaux de tokens. Les API indexées restent des alternatives ; voir le tableau ci-dessous. Les sources brutes/indexées mixtes vérifient indépendamment des fenêtres limitées ; deux fournisseurs indépendants restent le défaut, pas des alias d'un même opérateur. La hauteur de base d'un nœud, les informations de blockchain ORDnet et un relais EVM pour un canal non EVM ne sont pas des preuves de réception. Monero nécessite toujours un wallet-RPC en consultation seule lié au projet. |
| healthy_endpoints | integer | on-chain | Endpoints correspondants opérationnels, pas le nombre de fournisseurs indépendants. |
| usable_independent_providers / required_independent_providers | integer | on-chain | Emplacements de vérification utilisables, limités à deux. required_independent_providers est le réglage de blockchain : 2 par défaut, ou 1 après choix explicite de l'administrateur. Le mode à deux fournisseurs exige des clés fournisseur ET des hôtes différents. Les sources désactivées, périmées (plus de dix minutes) ou en pause ne remplissent pas d'emplacement. Lightning utilise ses propres règles de connexion. |
| last_checked_at | timestamp | null | on-chain | Dernier contrôle d'état de l'endpoint correspondant, distinct de l'heure d'évaluation. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/wallets" \
--header "Authorization: Bearer $WHOLLY_TOKEN"// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/wallets", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/wallets");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/wallets",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"data": [
{
"id": "55555555-5555-4555-8555-555555555555",
"project_id": "11111111-1111-4111-8111-111111111111",
"native_asset_id": "10000000-0000-4000-8000-000000000003",
"chain_slug": "ethereum",
"network": "mainnet",
"asset_symbol": "ETH",
"asset_name": "Ethereum",
"status": "active",
"label": "Primary Ethereum wallet",
"public_key": "0x…",
"primary_address": "0x…",
"derivation_scheme": "bip44",
"address_format": "eip55",
"backup_confirmed_at": "2026-08-31T17:00:00Z",
"last_secret_revealed_at": null,
"secret_reveal_count": 0,
"next_receive_index": 43,
"last_scanned_height": 23123456,
"last_scanned_at": "2026-08-31T18:05:00Z",
"last_error": null,
"balances": [
{ "wallet_id": "55555555-5555-4555-8555-555555555555", "asset_id": "10000000-0000-4000-8000-000000000003", "project_enabled": true, "tracking_active": true, "asset_kind": "native", "contract_address": null, "symbol": "ETH", "name": "Ethereum", "decimals": 18, "coingecko_id": "ethereum", "balance": "0.125", "balance_atomic": "125000000000000000", "price_usd": "4500", "value_usd": "562.50", "status": "fresh", "checked_at": "2026-08-31T18:05:00Z", "last_error": null },
{ "wallet_id": "55555555-5555-4555-8555-555555555555", "asset_id": "10000000-0000-4000-8000-000000000099", "project_enabled": false, "tracking_active": true, "asset_kind": "token", "contract_address": "0xA0b86991c6218b36c1d19d4a2e9eb0cE3606eB48", "symbol": "USDC", "name": "USDC", "decimals": 6, "coingecko_id": "usd-coin", "balance": null, "balance_atomic": null, "price_usd": "1", "value_usd": null, "status": "pending", "checked_at": null, "last_error": null }
],
"total_value_usd": "562.50",
"balance_status": "fresh",
"balance_checked_at": "2026-08-31T18:05:00Z",
"recent_payments": [
{ "invoice_public_id": "0a6a98db-d93d-48ee-8c3c-fd45f90c4a50", "chain_slug": "ethereum", "symbol": "USDC", "transaction_id": "0x…", "event_index": 0, "amount": "25", "status": "final", "confirmations": 12, "observed_at": "2026-08-31T18:04:00Z" }
],
"created_at": "2026-08-31T16:00:00Z",
"updated_at": "2026-08-31T18:05:00Z"
}
]
}POSTCréer une facture/v1/projects/{project_id}/stores/{store_id}/invoicesLecture + écriture
Crée atomiquement une facture avec destinations de portefeuille, devis exacts récents, historique d'audit et entrées dans la file de notifications sortantes. Rejouer les mêmes octets bruts du corps avec le même identifiant et Idempotency-Key renvoie la facture d'origine.
- payment_methods filtre les moyens activés du magasin pour cette facture uniquement. Omis/null conserve tous les moyens du magasin ; [] est invalide. Trouve l'indication chain_slug et les symboles affichés dans Projet → Magasins → Moyens de paiement. La liste API payment-assets fournit chain_slug, asset.symbol et asset.id. Utilise {chain_slug: ethereum, asset_tickers: [USDC, USDT]} pour les tokens Ethereum acceptés ; BTC et PEPE fonctionnent de la même façon sur leurs blockchains sélectionnées. Les symboles sont insensibles à la casse, limités à la blockchain et résolus uniquement dans le magasin. Deux contrats acceptés de même symbole renvoient 400 au lieu d'en choisir un, même si l'un n'est pas prêt ; utilise asset_ids dans ce cas. Les actifs natifs, tokens du catalogue et personnalisés suivent les mêmes règles. Chaque blockchain/canal peut apparaître une fois ; 64 moyens finaux maximum. Version commerçant 5.4.0+ : les choix inconnus, désactivés, de mauvaise blockchain ou non acceptés sont ignorés. Si toute la sélection n'a aucune correspondance active acceptée, les réglages du magasin s'appliquent ; sinon, seuls les choix correspondants sont utilisés. Une entrée limitée à la blockchain inclut chaque actif on-chain actif accepté. Les moyens actifs sélectionnés nécessitent des portefeuilles valides, adaptateurs de scanner installés et taux fiables. Depuis 6.0.6, les scanners indisponibles, pauses et contrôles d'état en attente/périmés ne bloquent pas la création de factures et ne retirent pas les moyens on-chain configurés. La détection réessaie automatiquement ; le règlement exige toujours le quorum des fournisseurs et les confirmations. Surveille receive_readiness et garde les fournisseurs disponibles : une facture peut rester non vérifiée jusqu'au rétablissement des scanners. L'allocation de sous-adresses Monero et la génération BOLT11 Lightning nécessitent toujours leur service externe de portefeuille/nœud. Les échecs renvoient error.message et error.details.payment_methods avec chain_slug, asset_ticker, reason_code et, pour le diagnostic scanner, required_endpoint_role, healthy_endpoints et required_independent_providers. TRON accepte l'historique indexé ou les API prises en charge de blocs natifs solidifiés bruts ; l'état de base seul ne prouve pas la compatibilité scanner. Les échecs de prix identifient l'actif/la devise. Rien n'active un actif non accepté ni ne change la politique du magasin. Sur les versions commerçant antérieures à 5.4.0, les choix explicites inconnus/inactifs échouent. Les moyens des factures existantes ne s'élargissent jamais lorsque les réglages du magasin changent. Lightning doit être sélectionné séparément. Les rejeux gardent les moyens d'origine et changer la sélection avec le même Idempotency-Key renvoie 409.
- checkout_appearance prend en charge tous les réglages de présentation listés ci-dessus. Les champs omis sont hérités, les tableaux remplacent et les champs de message imbriqués fusionnent ; un objet de message vide efface ce périmètre. Le design résolu et les images sont enregistrés pour cette facture sans modifier le magasin. Lis appearance dans le JSON du paiement public pour examiner le résultat. La requête entière est limitée à 32 KiB et les réglages résolus à 20 KiB.
- Modifier checkout_appearance avec le même Idempotency-Key renvoie 409 ; réessaie avec des octets bruts identiques. L'apparence ne change pas les montants, taux, actifs acceptés, confirmations requises, statut réel ni permissions d'intégration. Aucun HTML, CSS, script ni récupération d'image distante.
- exchange_rate_spread_percent remplace la valeur par défaut du magasin pour cette facture : omets-le ou envoie null pour hériter, ou envoie "0" pour le désactiver. Les devis des factures existantes ne changent jamais.
- Le spread s'applique avant l'arrondi au supérieur. Les frais restent basés sur le montant fiat d'origine de la facture, hors spread.
- Envoie toujours expected_amount ou expected_amount_atomic renvoyé. L'arrondi se fait au supérieur, limité par la précision de l'actif, 0.1 % du montant et une unité monétaire fiat mineure.
- Les nouvelles tentatives doivent garder le même identifiant, Idempotency-Key et les octets exacts du corps. Changer le spread avec la même clé renvoie 409 idempotency_conflict.
- Un rejeu exact est vérifié avant un nouveau devis, le DNS de callback ou la préparation d'adresses. Le périmètre de l'identifiant et l'autorisation projet/magasin sont toujours vérifiés à chaque requête.
- Un ipn_url effectif nécessite le secret de signature IPN du magasin. Les champs inconnus du corps sont refusés.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_MERCHANT_API_TOKEN |
| Idempotency-Key | obligatoire | 1–128 caractères ASCII visibles uniques, sans espaces. |
| Content-Type | recommandé | application/json. Le gestionnaire actuel du corps brut analyse le JSON sans imposer le type de média. |
| Accept | recommandé | application/json |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| project_id | path UUID | Copie l'identifiant API du projet dans Projet → Réglages → Identifiants API. Il doit être attribué à l'identifiant d'accès ; un identifiant lisible de projet n'est pas accepté. |
| store_id | path UUID | Copie l'identifiant API du magasin dans Projet → Magasins → sélectionne un magasin → Général → Identifiants API. Obligatoire même pour le magasin par défaut ; il doit être activé et appartenir à project_id. |
Corps de création de facture
| Champ | Type | Présence | Description |
|---|---|---|---|
| amount | string | obligatoire | Chaîne décimale simple non signée ; aucun signe ni exposant, jusqu'à 48 chiffres entiers et 30 décimales. Positive par défaut. Un magasin peut autoriser les factures de montant nul dans Magasins → Facture ; les totaux nuls sont réglés sans recevoir de fonds, allouer d'adresses ni frais de traitement. |
| currency | string | null | facultatif | Devise fiat prise en charge sur trois lettres, normalisée en majuscules. Omise ou null, elle hérite de la devise de facture du magasin. La création nécessite aussi un taux de conversion de facturation disponible indépendamment. |
| payment_methods | InvoicePaymentSelection[] | null | facultatif | Sélectionne les moyens activés du magasin pour cette facture. Version commerçant 5.4.0+ : ignore les choix inconnus/inactifs/non acceptés ; sans correspondance, utilise les réglages du magasin. Omis/null utilise aussi les réglages du magasin ; [] est invalide. N'active jamais de moyen ni ne change les réglages du magasin. Voir le schéma de sélection ci-dessous. |
| order_id | string | null | facultatif | Référence de commande du commerçant, 1–128 caractères après suppression des espaces externes ; caractères de contrôle refusés. |
| string | null | facultatif | E-mail client réservé au commerçant, normalisé en adresse ASCII utilisable de 254 caractères maximum. Omis ou null, aucun e-mail n'est enregistré. | |
| description | string | null | facultatif | Description visible par le client, 1–500 caractères ; sauts de ligne et tabulations autorisés. |
| expires_in_seconds | integer | null | facultatif | Durée du devis de facture de 300 à 86 400 secondes ; omise ou null, hérite de la politique du magasin. |
| exchange_rate_spread_percent | decimal string | null | facultatif | Majoration du devis de 0 à 100, deux décimales maximum. Omise ou null, hérite du réglage du magasin ; "0" la désactive pour cette facture. Appliquée avant l'arrondi au supérieur, puis verrouillée. Ne change ni le montant fiat de facture ni la base des frais de traitement. |
| underpayment_tolerance_percent | decimal string | null | facultatif | Manque accepté de 0 à 99.99, deux décimales maximum. Omis ou null, hérite du réglage du magasin. |
| ipn_url | string | null | facultatif | Callback HTTPS public, 2 048 octets maximum, sans identifiants ni fragment. Remplace le réglage du magasin ; null/omis en hérite. |
| redirect_url | string | null | facultatif | URL HTTPS de succès utilisée après règlement, 2 048 octets maximum, sans identifiants intégrés. Omise ou null, hérite du réglage du magasin et ne peut pas l'effacer. |
| cancel_url | string | null | facultatif | URL HTTPS de retour utilisée si le paiement se termine sans succès. Omise ou null, hérite du réglage du magasin et ne peut pas l'effacer. |
| redirect_automatically | boolean | null | facultatif | Omis ou null, hérite de la politique du magasin. true nécessite un redirect_url effectif. |
| language | string | null | facultatif | Étiquette BCP 47 anglaise ou allemande, comme en, de ou de-DE ; omise ou null, hérite de la politique du magasin. |
| checkout_appearance | CheckoutAppearanceOverride | null | facultatif | Réglages de présentation partiels pour cette facture. Omis/null suit le design actuel du magasin. Un objet, y compris {}, fige le design résolu et les images à la création. Voir le schéma de personnalisation ci-dessous ; aucun réglage financier, HTML, CSS, JavaScript ni URL d'image distante. |
| metadata | object | null | facultatif | Objet JSON réservé au commerçant ; omis ou null devient {}, 4 096 octets encodés maximum et cinq niveaux imbriqués. firstname, lastname, street, street2, zip, city, country, countryiso2, company et vatid sont validés, normalisés et projetés dans les champs récapitulatifs client. |
InvoicePaymentSelection · choisir les blockchains et actifs du magasin
| Champ | Type | Présence | Description |
|---|---|---|---|
| chain_slug | string | obligatoire | Copie chain_slug dans Projet → Magasins → Moyens de paiement, ou lis-le depuis GET /v1/projects/{project_id}/stores/{store_id}/payment-assets, par exemple ethereum, base ou bitcoin. Une paire blockchain/canal ne peut apparaître qu'une fois. |
| asset_ids | UUID[] | null | facultatif | UUID asset.id on-chain, pas des adresses de contrat ni identifiants de moyens de paiement de facture. Utilise ceci OU asset_tickers. Omets les deux sélecteurs pour tous les actifs actifs acceptés sur cette blockchain. [] et les identifiants dupliqués/nuls sont invalides. En 5.4.0+, ignore les identifiants non actifs/acceptés sur cette blockchain dans ce magasin ; une sélection sans aucune correspondance utilise les réglages du magasin. |
| asset_tickers | string[] | null | facultatif | Version commerçant 5.3.0+. Symboles comme BTC, USDC ou PEPE, limités à chain_slug et ce magasin. 1–64 symboles uniques ; espaces externes supprimés, insensibles à la casse, 1–40 lettres/chiffres/points/traits de soulignement/tirets ASCII. Utilise ceci OU asset_ids. En 5.4.0+, ignore les symboles inconnus/inactifs/non acceptés. Les symboles acceptés ambigus échouent toujours : utilise asset_ids. Les moyens actifs sélectionnés nécessitent des portefeuilles et prix valides ; les pannes temporaires de scanner on-chain ne bloquent pas la création depuis 6.0.6. Lightning n'accepte facultativement que BTC. |
| payment_rail | onchain | lightning | facultatif | onchain par défaut. Pour choisir Bitcoin Lightning, utilise {chain_slug: bitcoin, payment_rail: lightning} sans asset_ids ; asset_tickers peut facultativement être [BTC]. Bitcoin on-chain n'inclut pas Lightning. La connexion Lightning du magasin doit déjà être activée et prête. |
CheckoutAppearanceOverride · tous les champs facultatifs
| Champ | Type | Présence | Description |
|---|---|---|---|
| inherit_default_store | boolean | facultatif | true sélectionne comme base le design du magasin par défaut du projet ; sinon, utilise celui effectif du magasin cible. Les personnalisations sont ensuite appliquées et enregistrées indépendamment ; l'indicateur résolu de facture vaut false. |
| title | string | facultatif | Titre de paiement, 120 caractères maximum. Vide utilise le titre standard. |
| intro / outro | string | facultatif | Texte brut, 2 000 caractères maximum chacun. Intro apparaît en haut, Outro en bas dans chaque état. Les sauts de ligne sont conservés ; les URL sûres du texte deviennent des liens. Une chaîne vide efface. L'ancien customer_message est accepté comme alias de intro ; n'envoie pas les deux. |
| intro_font_size / outro_font_size | integer | facultatif | Pixels : 12, 14, 16, 18, 20 ou 24. 16 par défaut sauf héritage différent. |
| theme | system | light | dim | dark | facultatif | Suis l'appareil du client ou utilise un thème fixe. |
| accent_color / background_color / card_color / button_color | string | facultatif | #RRGGBB. Fond, carte et bouton peuvent être vides pour des couleurs automatiques. Le contraste du texte est automatique. |
| logo_size / logo_alignment | string | facultatif | small, medium ou large ; left ou center. |
| images | object | facultatif | Clés logo_light, logo_dark, favicon. Une clé omise conserve l'image de base ; null la supprime. Un objet {store_id: UUID, kind?: logo_light|logo_dark|favicon} réutilise l'image téléversée effective de ce magasin dans le MÊME projet. kind utilise par défaut la clé cible. Téléverse d'abord dans Magasin → Page de paiement ; copie l'identifiant API du magasin dans Général → Identifiants API. Les images manquantes ou identifiants d'autres projets renvoient 400. Aucune URL externe ni donnée d'image acceptée. |
| show_order_id / show_description / details_expanded | boolean | facultatif | Affiche les détails de l'identifiant de commande et une description en texte brut sous le titre. details_expanded ouvre les détails dès le départ. Affichage uniquement, pas masquage des données. |
| show_project_name / show_store_name | boolean | facultatif | Version commerçant 5.6.0+ : affiche ou masque chaque nom dans l'en-tête de paiement client. Les deux valent true par défaut. Également disponible dans Magasin → Page de paiement ; hérité et enregistré dans l'instantané de facture comme les autres réglages d'apparence. Affichage uniquement, pas masquage des données. |
| featured_chains | string[] | facultatif | Slugs de blockchain ordonnés, 60 valeurs uniques maximum (lettres minuscules, chiffres, tirets ; jusqu'à 64 caractères). [] efface. Seuls les moyens disponibles de la facture sont réordonnés. |
| featured_asset_ids / default_asset_id | UUID[] / UUID|null | facultatif | Jusqu'à 100 identifiants d'actifs uniques ordonnés ; [] efface. L'actif par défaut peut être null. Les identifiants viennent de payment-assets, pas des intentions de paiement. Ils n'activent jamais de moyens ; les paiements reçus et préférences client valides sont prioritaires. |
| messages | object | facultatif | Objets en/de avec chaînes simples waiting, confirming, paid, underpaid, expired (500 caractères chacune). Seules les langues/états fournis changent ; {} efface tous les messages, {en:{}} efface l'anglais et une chaîne d'état vide efface cet état. L'anglais est la langue de repli. Ne remplace pas le statut réel. |
| support_email | string | facultatif | E-mail ASCII, 254 caractères maximum. Vide efface. |
| support_url / terms_url / privacy_url | string | facultatif | URL HTTPS jusqu'à 2 048 caractères, sans identifiants. Vide efface. Les liens s'ouvrent dans une nouvelle fenêtre. |
| return_button_text | string | facultatif | Libellé jusqu'à 60 caractères. Utilise redirect_url/cancel_url/redirect_automatically/language de premier niveau pour le comportement de la facture. |
Résumé de facture
| Champ | Type | Présence | Description |
|---|---|---|---|
| id | UUID | toujours | UUID interne de facture. Ne l'utilise pas dans les chemins de détail commerçant ni de paiement. |
| invoice_id | UUID | toujours | UUID public de facture utilisé dans les chemins de détail commerçant et de paiement. |
| project_id | UUID | toujours | Projet propriétaire. |
| store_id | UUID | toujours | Magasin propriétaire. |
| source | manual | api | toujours | Comment la facture a été créée. |
| order_id | string | null | toujours | Référence de commande du commerçant. |
| string | null | toujours | E-mail client réservé au commerçant. Jamais renvoyé par la page de paiement publique. | |
| customer_name | string | null | toujours | Nom d'affichage dérivé des métadonnées privées firstname, lastname et company. |
| customer_address | string | null | toujours | Adresse du commerçant sur une ligne dérivée des métadonnées privées company, street, street2, zip, city, country, countryiso2 et vatid. |
| description | string | null | toujours | Description visible par le client. |
| amount | decimal string | toujours | Montant canonique de la facture. |
| currency | string | toujours | Code normalisé de devise/actif de la facture. |
| exchange_rate_spread_percent | decimal string | toujours | Spread de devis verrouillé : la valeur définie à la création, ou celle du magasin par défaut si omise. Appliqué avant l'arrondi au supérieur ; ne change jamais pour cette facture. |
| underpayment_tolerance_percent | decimal string | toujours | Pourcentage immuable de manque accepté enregistré à la création de la facture. |
| status | invoice status | toujours | new, processing, settled, expired, invalid ou cancelled. |
| amount_status | amount status | toujours | none, partial, paid ou overpaid. Une facture de montant nul explicitement autorisée est réglée avec none et sans moyen de paiement. |
| timing_status | timing status | toujours | on_time ou late. |
| resolution | resolution | toujours | automatic, manually_settled ou manually_invalidated. |
| sequence | integer | toujours | Séquence monotone d'état de la facture, à partir de 1. |
| winning_payment_intent_id | UUID | null | toujours | Moyen de paiement ayant résolu la facture, lorsqu'il est sélectionné. |
| expires_at | RFC 3339 timestamp | toujours | Échéance du devis/paiement. |
| monitoring_expires_at | RFC 3339 timestamp | toujours | Dernière échéance configurée de surveillance tardive parmi les moyens de paiement. |
| settled_at | timestamp | null | toujours | Heure de règlement lorsqu'elle est réglée. |
| cancelled_at | timestamp | null | toujours | Heure d'annulation lorsqu'elle est annulée. |
| archived_at | timestamp | null | toujours | Heure d'archivage lorsqu'elle est archivée. |
| created_at | RFC 3339 timestamp | toujours | Heure de création. |
| updated_at | RFC 3339 timestamp | toujours | Heure de dernière mise à jour de l'état. |
Ajouts au détail de facture
| Champ | Type | Présence | Description |
|---|---|---|---|
| ipn_url | string | null | toujours | Cible IPN effective par facture. Réponse commerçant uniquement ; omise du paiement public. |
| redirect_url | string | null | toujours | URL de succès effective utilisée après règlement. |
| cancel_url | string | null | toujours | URL de retour effective utilisée lorsque le paiement se termine sans succès. |
| redirect_automatically | boolean | toujours | Indique si la page de paiement doit rediriger automatiquement après réussite. |
| checkout_language | string | toujours | Étiquette de langue effective de la page de paiement. |
| metadata | object | toujours | Métadonnées du commerçant. Jamais renvoyées par le paiement public. |
| payment_intents | PaymentIntent[] | toujours | Moyens de paiement chiffrés et état de surveillance. |
PaymentIntent
| Champ | Type | Présence | Description |
|---|---|---|---|
| id | UUID | toujours | Identifiant d'intention de paiement ; aussi utilisé comme intent_id du QR de paiement. |
| payment_rail | onchain | lightning | toujours | Transport de facture. Bitcoin on-chain et Lightning peuvent partager asset_id ; utilise l'id d'intention avec ce champ, pas le symbole seul. Diffère du payment_rail de scanner dans le catalogue d'actifs. |
| bolt11 | string | null | toujours | Demande de paiement Lightning, sinon null. Paie cette demande avec un portefeuille Lightning ; n'envoie jamais de fonds on-chain à son hash de paiement. |
| asset_id | UUID | toujours | Identifiant de l'actif de paiement configuré. |
| asset_key | string | toujours | Clé canonique d'actif au format CAIP. |
| chain_slug | string | toujours | Identifiant de blockchain Wholly Crypto. |
| network | string | toujours | Réseau configuré, actuellement mainnet pour les actifs de paiement pris en charge. |
| caip_network_id | string | toujours | Identifiant réseau canonique CAIP-2. |
| caip_asset_id | string | null | toujours | Identifiant canonique CAIP-19 si enregistré. |
| symbol | string | toujours | Symbole de l'actif. |
| asset_decimals | integer | toujours | Précision en unités atomiques. Lightning BTC utilise 11 (millisatoshis), pas les 8 de Bitcoin on-chain. Les devis sont en satoshis entiers ; les réceptions gardent la précision au millisatoshi. |
| status | intent status | toujours | pending, partial, paid, overpaid, expired ou invalid. |
| finality_mode | confirmations | finalized | toujours | Politique de finalité. |
| required_confirmations | integer | toujours | Confirmations requises, si applicable. |
| quote_rate | decimal string | toujours | Unités de l'actif pour une unité de devise de facture, spread verrouillé compris. Par exemple 1.02 USDC par USD. Pas le taux inverse. |
| quote_details | object | null | toujours | Provenance du devis verrouillé : reference_rate avant spread, unrounded_payment_amount, rounding_adjustment, pricing_provider, asset_provider, pricing_fetched_at et asset_fetched_at. Null sur les anciennes factures ; aucune valeur historique n'est inventée. |
| expected_amount | decimal string | toujours | Montant exact verrouillé de l'actif à payer après spread et arrondi au supérieur. Depuis 4.1.1, les stablecoins fiat reconnus et vérifiés (comme USDC, USDT, DAI, USDS, EURC) sont arrondis au supérieur à deux décimales maximum ; 1.321 devient 1.33, jamais 1.32. C'est le montant attendu même avec une tolérance nulle. Les autres actifs gardent la précision adaptative. Les prix des factures existantes ne sont jamais recalculés. |
| expected_amount_atomic | integer string | toujours | Montant exact dans la plus petite unité de l'actif. |
| minimum_payment_amount | decimal string | toujours | Plus petit montant accepté comme payé après application de la tolérance de facture. |
| minimum_payment_amount_atomic | integer string | toujours | Seuil exact accepté dans la plus petite unité de l'actif. |
| received_amount | decimal string | toujours | Montant observé. |
| received_amount_atomic | integer string | toujours | Montant atomique observé. |
| confirmed_amount | decimal string | toujours | Montant confirmé/final. |
| confirmed_amount_atomic | integer string | toujours | Montant atomique confirmé/final. |
| destination_address | string | toujours | Adresse de réception on-chain, ou hash de paiement de 64 caractères pour Lightning. Utilise bolt11 pour payer via Lightning ; son hash n'est pas une adresse Bitcoin. |
| destination_tag | string | null | toujours | Référence publique de paiement obligatoire lorsque le réseau en utilise une : destination tag XRP, memo ID Stellar ou commentaire de facture TON. Null pour les réseaux à adresses uniques. |
| derivation_index | integer | toujours | Index enfant réservé du portefeuille ; uniquement dans le détail destiné au commerçant. |
| quote_expires_at | RFC 3339 timestamp | toujours | Expiration du devis. |
| monitoring_expires_at | RFC 3339 timestamp | toujours | Fin du suivi tardif pour ce moyen. |
| next_check_at | timestamp | null | toujours | Prochaine vérification programmée de la blockchain. |
| last_checked_at | timestamp | null | toujours | Dernière vérification de la blockchain. |
| last_chain_height | integer | null | toujours | Dernière hauteur fiable observée par le moniteur. |
| last_anchor_hash | string | null | toujours | Dernier hash d'ancrage/de bloc du moniteur. |
| last_monitor_error | string | null | toujours | Diagnostic de suivi sûr pour les opérateurs. |
| first_payment_at | timestamp | null | toujours | Heure de la première observation du paiement. |
| fully_paid_at | timestamp | null | toujours | Heure à laquelle le montant minimum accepté a été atteint pour la première fois. |
| finalized_at | timestamp | null | toujours | Heure à laquelle le paiement a satisfait la politique de finalité. |
PaymentMethodIssue
| Champ | Type | Présence | Description |
|---|---|---|---|
| chain_slug / asset_id / asset_ticker | string / UUID / string | si connu | Identifie la blockchain et l'actif concernés. Lightning peut omettre asset_id. |
| reason_code | string | toujours | scanner_provider_quorum, scanner_not_checked, scanner_unavailable, wallet_missing, wallet_disabled, wallet_backup_required, wallet_key_unavailable, wallet_activation_required, monero_binding_unavailable, rate_unavailable, custom_rate_unavailable, lightning_unavailable, project_disabled, store_disabled, chain_disabled, asset_disabled ou asset_not_accepted. |
| message / action | string | si disponible | Explication pour le commerçant et identifiant d'action : chain_connections, wallets, rates, payment_methods, project_settings ou store_settings. Aucun identifiant ni URL privée de fournisseur. |
| required_endpoint_role | string | null | on-chain | Rôle API de scanner préféré (ancien champ). Utilise accepted_endpoint_roles pour la liste complète de compatibilité. L'état de base du nœud ne prouve pas la prise en charge de l'historique des paiements. |
| accepted_endpoint_roles | string[] | null | on-chain | Dialectes API compatibles, pas une preuve d'historique ou de capacité d'endpoint. node-rpc brut prend en charge BTC/BCH/LTC/DOGE/DASH et ZEC transparent (blocs décodés complets, 1–48 confirmations), TRX natif solidifié, ALGO natif via algod, XTZ via Octez, DOT Asset Hub finalisé via métadonnées SCALE et XLM natif via Stellar RPC avec identifiant de mémo de facture. Un historique élagué ou incomplet n'est pas admissible. Ces adaptateurs bruts n'ajoutent pas de canaux de tokens. Les API indexées restent des alternatives ; voir le tableau ci-dessous. Les sources brutes/indexées mixtes vérifient indépendamment des fenêtres limitées ; deux fournisseurs indépendants restent le défaut, pas des alias d'un même opérateur. La hauteur de base d'un nœud, les informations de blockchain ORDnet et un relais EVM pour un canal non EVM ne sont pas des preuves de réception. Monero nécessite toujours un wallet-RPC en consultation seule lié au projet. |
| healthy_endpoints | integer | on-chain | Endpoints correspondants opérationnels, pas le nombre de fournisseurs indépendants. |
| usable_independent_providers / required_independent_providers | integer | on-chain | Emplacements de vérification utilisables, limités à deux. required_independent_providers est le réglage de blockchain : 2 par défaut, ou 1 après choix explicite de l'administrateur. Le mode à deux fournisseurs exige des clés fournisseur ET des hôtes différents. Les sources désactivées, périmées (plus de dix minutes) ou en pause ne remplissent pas d'emplacement. Lightning utilise ses propres règles de connexion. |
| last_checked_at | timestamp | null | on-chain | Dernier contrôle d'état de l'endpoint correspondant, distinct de l'heure d'évaluation. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new invoice.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: order-1042-attempt-1' \
--header 'Content-Type: application/json' \
--data-raw '{
"payment_methods": [
{
"chain_slug": "bitcoin",
"asset_tickers": [
"BTC"
]
}
],
"amount": "49.90",
"currency": "USD",
"order_id": "order-1042",
"email": "ada@example.com",
"description": "Annual plan",
"underpayment_tolerance_percent": "1",
"ipn_url": "https://merchant.example/wholly/ipn",
"redirect_url": "https://merchant.example/orders/1042",
"cancel_url": "https://merchant.example/cart",
"redirect_automatically": true,
"language": "en",
"metadata": {
"cart_id": "cart-681",
"firstname": "Ada",
"lastname": "Lovelace",
"street": "12 Example Street",
"street2": "Suite 2",
"zip": "10115",
"city": "Berlin",
"country": "Germany",
"countryiso2": "DE",
"company": "Example GmbH",
"vatid": "DE123456789"
},
"exchange_rate_spread_percent": "0.5",
"checkout_appearance": {
"title": "Complete your order",
"intro": "Thanks for choosing our annual plan.",
"outro": "Questions? https://merchant.example/help",
"intro_font_size": 18,
"outro_font_size": 14,
"theme": "light",
"accent_color": "#1768CE",
"messages": {
"en": {
"paid": "Your order is ready."
}
}
}
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new invoice.
const body = `{
"payment_methods": [
{
"chain_slug": "bitcoin",
"asset_tickers": [
"BTC"
]
}
],
"amount": "49.90",
"currency": "USD",
"order_id": "order-1042",
"email": "ada@example.com",
"description": "Annual plan",
"underpayment_tolerance_percent": "1",
"ipn_url": "https://merchant.example/wholly/ipn",
"redirect_url": "https://merchant.example/orders/1042",
"cancel_url": "https://merchant.example/cart",
"redirect_automatically": true,
"language": "en",
"metadata": {
"cart_id": "cart-681",
"firstname": "Ada",
"lastname": "Lovelace",
"street": "12 Example Street",
"street2": "Suite 2",
"zip": "10115",
"city": "Berlin",
"country": "Germany",
"countryiso2": "DE",
"company": "Example GmbH",
"vatid": "DE123456789"
},
"exchange_rate_spread_percent": "0.5",
"checkout_appearance": {
"title": "Complete your order",
"intro": "Thanks for choosing our annual plan.",
"outro": "Questions? https://merchant.example/help",
"intro_font_size": 18,
"outro_font_size": 14,
"theme": "light",
"accent_color": "#1768CE",
"messages": {
"en": {
"paid": "Your order is ready."
}
}
}
}`;
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "order-1042-attempt-1",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new invoice.
$body = <<<'JSON'
{
"payment_methods": [
{
"chain_slug": "bitcoin",
"asset_tickers": [
"BTC"
]
}
],
"amount": "49.90",
"currency": "USD",
"order_id": "order-1042",
"email": "ada@example.com",
"description": "Annual plan",
"underpayment_tolerance_percent": "1",
"ipn_url": "https://merchant.example/wholly/ipn",
"redirect_url": "https://merchant.example/orders/1042",
"cancel_url": "https://merchant.example/cart",
"redirect_automatically": true,
"language": "en",
"metadata": {
"cart_id": "cart-681",
"firstname": "Ada",
"lastname": "Lovelace",
"street": "12 Example Street",
"street2": "Suite 2",
"zip": "10115",
"city": "Berlin",
"country": "Germany",
"countryiso2": "DE",
"company": "Example GmbH",
"vatid": "DE123456789"
},
"exchange_rate_spread_percent": "0.5",
"checkout_appearance": {
"title": "Complete your order",
"intro": "Thanks for choosing our annual plan.",
"outro": "Questions? https://merchant.example/help",
"intro_font_size": 18,
"outro_font_size": 14,
"theme": "light",
"accent_color": "#1768CE",
"messages": {
"en": {
"paid": "Your order is ready."
}
}
}
}
JSON;
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: order-1042-attempt-1", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new invoice.
headers = {
"Idempotency-Key": "order-1042-attempt-1",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"payment_methods": [
{
"chain_slug": "bitcoin",
"asset_tickers": [
"BTC"
]
}
],
"amount": "49.90",
"currency": "USD",
"order_id": "order-1042",
"email": "ada@example.com",
"description": "Annual plan",
"underpayment_tolerance_percent": "1",
"ipn_url": "https://merchant.example/wholly/ipn",
"redirect_url": "https://merchant.example/orders/1042",
"cancel_url": "https://merchant.example/cart",
"redirect_automatically": true,
"language": "en",
"metadata": {
"cart_id": "cart-681",
"firstname": "Ada",
"lastname": "Lovelace",
"street": "12 Example Street",
"street2": "Suite 2",
"zip": "10115",
"city": "Berlin",
"country": "Germany",
"countryiso2": "DE",
"company": "Example GmbH",
"vatid": "DE123456789"
},
"exchange_rate_spread_percent": "0.5",
"checkout_appearance": {
"title": "Complete your order",
"intro": "Thanks for choosing our annual plan.",
"outro": "Questions? https://merchant.example/help",
"intro_font_size": 18,
"outro_font_size": 14,
"theme": "light",
"accent_color": "#1768CE",
"messages": {
"en": {
"paid": "Your order is ready."
}
}
}
}""".encode("utf-8")
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 201 nouvelle facture ; 200 répétition idempotente exacte
{
"data": {
"id": "2f798f9f-01f2-42f0-9d10-5581d6116b4c",
"invoice_id": "0a6a98db-d93d-48ee-8c3c-fd45f90c4a50",
"project_id": "11111111-1111-4111-8111-111111111111",
"store_id": "22222222-2222-4222-8222-222222222222",
"source": "api",
"order_id": "order-1042",
"email": "ada@example.com",
"customer_name": "Ada Lovelace · Example GmbH",
"customer_address": "Example GmbH · 12 Example Street · Suite 2 · 10115 Berlin · Germany (DE) · VAT DE123456789",
"description": "Annual plan",
"amount": "49.9",
"currency": "USD",
"exchange_rate_spread_percent": "0.5",
"underpayment_tolerance_percent": "1",
"status": "new",
"amount_status": "none",
"timing_status": "on_time",
"resolution": "automatic",
"sequence": 1,
"winning_payment_intent_id": null,
"expires_at": "2026-08-31T18:15:00Z",
"monitoring_expires_at": "2026-09-07T18:15:00Z",
"settled_at": null,
"cancelled_at": null,
"archived_at": null,
"created_at": "2026-08-31T18:00:00Z",
"updated_at": "2026-08-31T18:00:00Z",
"ipn_url": "https://merchant.example/wholly/ipn",
"redirect_url": "https://merchant.example/orders/1042",
"cancel_url": "https://merchant.example/cart",
"redirect_automatically": true,
"checkout_language": "en",
"metadata": { "cart_id": "cart-681", "firstname": "Ada", "lastname": "Lovelace", "street": "12 Example Street", "street2": "Suite 2", "zip": "10115", "city": "Berlin", "country": "Germany", "countryiso2": "DE", "company": "Example GmbH", "vatid": "DE123456789" },
"payment_intents": [
{
"id": "33333333-3333-4333-8333-333333333333",
"asset_id": "10000000-0000-4000-8000-000000000001",
"asset_key": "bip122:000000000019d6689c085ae165831e93/slip44:0",
"chain_slug": "bitcoin",
"network": "mainnet",
"caip_network_id": "bip122:000000000019d6689c085ae165831e93",
"caip_asset_id": "bip122:000000000019d6689c085ae165831e93/slip44:0",
"symbol": "BTC",
"asset_decimals": 8,
"status": "pending",
"finality_mode": "confirmations",
"required_confirmations": 1,
"quote_rate": "0.000009218",
"quote_details": null,
"expected_amount": "0.00046",
"expected_amount_atomic": "46000",
"minimum_payment_amount": "0.0004554",
"minimum_payment_amount_atomic": "45540",
"received_amount": "0",
"received_amount_atomic": "0",
"confirmed_amount": "0",
"confirmed_amount_atomic": "0",
"destination_address": "bc1q…example",
"destination_tag": null,
"derivation_index": 42,
"quote_expires_at": "2026-08-31T18:15:00Z",
"monitoring_expires_at": "2026-09-07T18:15:00Z",
"next_check_at": "2026-08-31T18:00:00Z",
"last_checked_at": null,
"last_chain_height": null,
"last_anchor_hash": null,
"last_monitor_error": null,
"first_payment_at": null,
"fully_paid_at": null,
"finalized_at": null
}
]
},
"links": {
"checkout": "https://pay.example.com/invoice/0a6a98db-d93d-48ee-8c3c-fd45f90c4a50"
}
}GETLister les factures/v1/projects/{project_id}/invoicesLecture seule
Renvoie une page compacte de résumés des factures du périmètre autorisé, de la plus récente à la plus ancienne, avec l'email et les champs client réservés au commerçant et tirés des métadonnées reconnues. La recherche et les filtres de statut et de magasin sont évalués côté serveur ; la réponse inclut total et has_more pour une pagination prévisible.
- Triés par created_at décroissant, puis par id interne décroissant.
- Les éléments de la liste sont des objets InvoiceSummary ; email, customer_name et customer_address sont réservés au commerçant. Appelle le détail pour les métadonnées brutes et les intentions de paiement.
- Pour la page suivante, définis offset sur pagination.offset + pagination.limit uniquement quand has_more vaut true.
- Le nombre et la page sont lus depuis un même instantané de base de données à lecture répétable ; les écritures concurrentes apparaissent lors d'une requête ultérieure.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_MERCHANT_API_TOKEN |
| Accept | recommandé | application/json |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| project_id | path UUID | Projet activé attribué à l'identifiant. |
| store_id | query UUID | Filtre exact facultatif par magasin. |
| status | query enum | Facultatif : new, processing, settled, expired, invalid ou cancelled. |
| search | query string | Préfixe facultatif d'ID de facture, d'ID de commande ou d'email sans distinction de casse ; UUID exact de facture ; ou sous-chaîne dans la description et les champs client reconnus. Toutes les clés de métadonnées et les valeurs textuelles, numériques et booléennes (y compris les objets/tableaux imbriqués) prennent aussi en charge une recherche indexée par préfixe de mot : chaque mot recherché doit correspondre, la ponctuation servant de séparateur. Espaces de début et de fin retirés, 100 caractères maximum, aucun caractère de contrôle. Les correspondances dans les métadonnées n'ajoutent pas de métadonnées brutes aux réponses de liste ; utilise le détail de la facture pour les lire. |
| limit | query integer | Facultatif 1–100 ; 50 par défaut. |
| offset | query integer | Facultatif, 0–1 000 000 ; valeur par défaut 0. |
Résumé de facture
| Champ | Type | Présence | Description |
|---|---|---|---|
| id | UUID | toujours | UUID interne de facture. Ne l'utilise pas dans les chemins de détail commerçant ni de paiement. |
| invoice_id | UUID | toujours | UUID public de facture utilisé dans les chemins de détail commerçant et de paiement. |
| project_id | UUID | toujours | Projet propriétaire. |
| store_id | UUID | toujours | Magasin propriétaire. |
| source | manual | api | toujours | Comment la facture a été créée. |
| order_id | string | null | toujours | Référence de commande du commerçant. |
| string | null | toujours | E-mail client réservé au commerçant. Jamais renvoyé par la page de paiement publique. | |
| customer_name | string | null | toujours | Nom d'affichage dérivé des métadonnées privées firstname, lastname et company. |
| customer_address | string | null | toujours | Adresse du commerçant sur une ligne dérivée des métadonnées privées company, street, street2, zip, city, country, countryiso2 et vatid. |
| description | string | null | toujours | Description visible par le client. |
| amount | decimal string | toujours | Montant canonique de la facture. |
| currency | string | toujours | Code normalisé de devise/actif de la facture. |
| exchange_rate_spread_percent | decimal string | toujours | Spread de devis verrouillé : la valeur définie à la création, ou celle du magasin par défaut si omise. Appliqué avant l'arrondi au supérieur ; ne change jamais pour cette facture. |
| underpayment_tolerance_percent | decimal string | toujours | Pourcentage immuable de manque accepté enregistré à la création de la facture. |
| status | invoice status | toujours | new, processing, settled, expired, invalid ou cancelled. |
| amount_status | amount status | toujours | none, partial, paid ou overpaid. Une facture de montant nul explicitement autorisée est réglée avec none et sans moyen de paiement. |
| timing_status | timing status | toujours | on_time ou late. |
| resolution | resolution | toujours | automatic, manually_settled ou manually_invalidated. |
| sequence | integer | toujours | Séquence monotone d'état de la facture, à partir de 1. |
| winning_payment_intent_id | UUID | null | toujours | Moyen de paiement ayant résolu la facture, lorsqu'il est sélectionné. |
| expires_at | RFC 3339 timestamp | toujours | Échéance du devis/paiement. |
| monitoring_expires_at | RFC 3339 timestamp | toujours | Dernière échéance configurée de surveillance tardive parmi les moyens de paiement. |
| settled_at | timestamp | null | toujours | Heure de règlement lorsqu'elle est réglée. |
| cancelled_at | timestamp | null | toujours | Heure d'annulation lorsqu'elle est annulée. |
| archived_at | timestamp | null | toujours | Heure d'archivage lorsqu'elle est archivée. |
| created_at | RFC 3339 timestamp | toujours | Heure de création. |
| updated_at | RFC 3339 timestamp | toujours | Heure de dernière mise à jour de l'état. |
Pagination des factures
| Champ | Type | Présence | Description |
|---|---|---|---|
| limit | integer | toujours | Taille effective de la page, 1–100. |
| offset | integer | toujours | Décalage effectif des lignes à partir de zéro, 0–1 000 000. |
| total | integer | toujours | Nombre total de lignes correspondant aux filtres de projet, magasin, statut et recherche dans l'instantané de la page. |
| has_more | boolean | toujours | True lorsque offset plus le nombre de lignes renvoyées est inférieur à total. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices?search=order-1042&status=processing&limit=50&offset=0" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Accept: application/json'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices?search=order-1042&status=processing&limit=50&offset=0", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
"Accept": "application/json"
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices?search=order-1042&status=processing&limit=50&offset=0");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Accept: application/json"],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Accept": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices?search=order-1042&status=processing&limit=50&offset=0",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"data": [
{
"id": "2f798f9f-01f2-42f0-9d10-5581d6116b4c",
"invoice_id": "0a6a98db-d93d-48ee-8c3c-fd45f90c4a50",
"project_id": "11111111-1111-4111-8111-111111111111",
"store_id": "22222222-2222-4222-8222-222222222222",
"source": "api",
"order_id": "order-1042",
"email": "ada@example.com",
"customer_name": "Ada Lovelace · Example GmbH",
"customer_address": "Example GmbH · 12 Example Street · Suite 2 · 10115 Berlin · Germany (DE) · VAT DE123456789",
"description": "Annual plan",
"amount": "49.9",
"currency": "USD",
"exchange_rate_spread_percent": "0.5",
"underpayment_tolerance_percent": "1",
"status": "processing",
"amount_status": "paid",
"timing_status": "on_time",
"resolution": "automatic",
"sequence": 3,
"winning_payment_intent_id": "33333333-3333-4333-8333-333333333333",
"expires_at": "2026-08-31T18:15:00Z",
"monitoring_expires_at": "2026-09-07T18:15:00Z",
"settled_at": null,
"cancelled_at": null,
"archived_at": null,
"created_at": "2026-08-31T18:00:00Z",
"updated_at": "2026-08-31T18:04:10Z"
}
],
"pagination": {
"limit": 50,
"offset": 0,
"total": 143,
"has_more": true
}
}GETRécupérer une facture/v1/projects/{project_id}/invoices/{invoice_id}Lecture seule
Renvoie le détail complet de la facture pour le commerçant et l'URL de paiement actuellement active. Utilise cette route pour l'interrogation périodique et le rapprochement.
- Une recherche limitée au périmètre autorisé renvoie volontairement invoice_not_found lorsque l'ID public n'appartient pas au projet autorisé.
- links.checkout utilise Magasin → Général → Domaines du magasin : d'abord le nom d'hôte pay actif de ce magasin, puis le choix de son magasin par défaut, puis le principal du système. Les hôtes retirés/en brouillon ou associés au mauvais service sont ignorés. Cela s'applique aussi aux réponses de création et MCP ; les liens sont résolus au moment de la réponse, y compris les répétitions idempotentes. Les liens des callbacks signés sont figés à la création de l'événement, pas réécrits lors des nouvelles tentatives. Ces préférences génèrent uniquement des liens ; elles ne redirigent pas le trafic et ne modifient pas les restrictions IP.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_MERCHANT_API_TOKEN |
| Accept | recommandé | application/json |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| project_id | path UUID | Projet activé attribué à l'identifiant. |
| invoice_id | path UUID | L'invoice_id renvoyé à la création/dans la liste, pas l'id interne. |
Résumé de facture
| Champ | Type | Présence | Description |
|---|---|---|---|
| id | UUID | toujours | UUID interne de facture. Ne l'utilise pas dans les chemins de détail commerçant ni de paiement. |
| invoice_id | UUID | toujours | UUID public de facture utilisé dans les chemins de détail commerçant et de paiement. |
| project_id | UUID | toujours | Projet propriétaire. |
| store_id | UUID | toujours | Magasin propriétaire. |
| source | manual | api | toujours | Comment la facture a été créée. |
| order_id | string | null | toujours | Référence de commande du commerçant. |
| string | null | toujours | E-mail client réservé au commerçant. Jamais renvoyé par la page de paiement publique. | |
| customer_name | string | null | toujours | Nom d'affichage dérivé des métadonnées privées firstname, lastname et company. |
| customer_address | string | null | toujours | Adresse du commerçant sur une ligne dérivée des métadonnées privées company, street, street2, zip, city, country, countryiso2 et vatid. |
| description | string | null | toujours | Description visible par le client. |
| amount | decimal string | toujours | Montant canonique de la facture. |
| currency | string | toujours | Code normalisé de devise/actif de la facture. |
| exchange_rate_spread_percent | decimal string | toujours | Spread de devis verrouillé : la valeur définie à la création, ou celle du magasin par défaut si omise. Appliqué avant l'arrondi au supérieur ; ne change jamais pour cette facture. |
| underpayment_tolerance_percent | decimal string | toujours | Pourcentage immuable de manque accepté enregistré à la création de la facture. |
| status | invoice status | toujours | new, processing, settled, expired, invalid ou cancelled. |
| amount_status | amount status | toujours | none, partial, paid ou overpaid. Une facture de montant nul explicitement autorisée est réglée avec none et sans moyen de paiement. |
| timing_status | timing status | toujours | on_time ou late. |
| resolution | resolution | toujours | automatic, manually_settled ou manually_invalidated. |
| sequence | integer | toujours | Séquence monotone d'état de la facture, à partir de 1. |
| winning_payment_intent_id | UUID | null | toujours | Moyen de paiement ayant résolu la facture, lorsqu'il est sélectionné. |
| expires_at | RFC 3339 timestamp | toujours | Échéance du devis/paiement. |
| monitoring_expires_at | RFC 3339 timestamp | toujours | Dernière échéance configurée de surveillance tardive parmi les moyens de paiement. |
| settled_at | timestamp | null | toujours | Heure de règlement lorsqu'elle est réglée. |
| cancelled_at | timestamp | null | toujours | Heure d'annulation lorsqu'elle est annulée. |
| archived_at | timestamp | null | toujours | Heure d'archivage lorsqu'elle est archivée. |
| created_at | RFC 3339 timestamp | toujours | Heure de création. |
| updated_at | RFC 3339 timestamp | toujours | Heure de dernière mise à jour de l'état. |
Ajouts au détail de facture
| Champ | Type | Présence | Description |
|---|---|---|---|
| ipn_url | string | null | toujours | Cible IPN effective par facture. Réponse commerçant uniquement ; omise du paiement public. |
| redirect_url | string | null | toujours | URL de succès effective utilisée après règlement. |
| cancel_url | string | null | toujours | URL de retour effective utilisée lorsque le paiement se termine sans succès. |
| redirect_automatically | boolean | toujours | Indique si la page de paiement doit rediriger automatiquement après réussite. |
| checkout_language | string | toujours | Étiquette de langue effective de la page de paiement. |
| metadata | object | toujours | Métadonnées du commerçant. Jamais renvoyées par le paiement public. |
| payment_intents | PaymentIntent[] | toujours | Moyens de paiement chiffrés et état de surveillance. |
PaymentIntent
| Champ | Type | Présence | Description |
|---|---|---|---|
| id | UUID | toujours | Identifiant d'intention de paiement ; aussi utilisé comme intent_id du QR de paiement. |
| payment_rail | onchain | lightning | toujours | Transport de facture. Bitcoin on-chain et Lightning peuvent partager asset_id ; utilise l'id d'intention avec ce champ, pas le symbole seul. Diffère du payment_rail de scanner dans le catalogue d'actifs. |
| bolt11 | string | null | toujours | Demande de paiement Lightning, sinon null. Paie cette demande avec un portefeuille Lightning ; n'envoie jamais de fonds on-chain à son hash de paiement. |
| asset_id | UUID | toujours | Identifiant de l'actif de paiement configuré. |
| asset_key | string | toujours | Clé canonique d'actif au format CAIP. |
| chain_slug | string | toujours | Identifiant de blockchain Wholly Crypto. |
| network | string | toujours | Réseau configuré, actuellement mainnet pour les actifs de paiement pris en charge. |
| caip_network_id | string | toujours | Identifiant réseau canonique CAIP-2. |
| caip_asset_id | string | null | toujours | Identifiant canonique CAIP-19 si enregistré. |
| symbol | string | toujours | Symbole de l'actif. |
| asset_decimals | integer | toujours | Précision en unités atomiques. Lightning BTC utilise 11 (millisatoshis), pas les 8 de Bitcoin on-chain. Les devis sont en satoshis entiers ; les réceptions gardent la précision au millisatoshi. |
| status | intent status | toujours | pending, partial, paid, overpaid, expired ou invalid. |
| finality_mode | confirmations | finalized | toujours | Politique de finalité. |
| required_confirmations | integer | toujours | Confirmations requises, si applicable. |
| quote_rate | decimal string | toujours | Unités de l'actif pour une unité de devise de facture, spread verrouillé compris. Par exemple 1.02 USDC par USD. Pas le taux inverse. |
| quote_details | object | null | toujours | Provenance du devis verrouillé : reference_rate avant spread, unrounded_payment_amount, rounding_adjustment, pricing_provider, asset_provider, pricing_fetched_at et asset_fetched_at. Null sur les anciennes factures ; aucune valeur historique n'est inventée. |
| expected_amount | decimal string | toujours | Montant exact verrouillé de l'actif à payer après spread et arrondi au supérieur. Depuis 4.1.1, les stablecoins fiat reconnus et vérifiés (comme USDC, USDT, DAI, USDS, EURC) sont arrondis au supérieur à deux décimales maximum ; 1.321 devient 1.33, jamais 1.32. C'est le montant attendu même avec une tolérance nulle. Les autres actifs gardent la précision adaptative. Les prix des factures existantes ne sont jamais recalculés. |
| expected_amount_atomic | integer string | toujours | Montant exact dans la plus petite unité de l'actif. |
| minimum_payment_amount | decimal string | toujours | Plus petit montant accepté comme payé après application de la tolérance de facture. |
| minimum_payment_amount_atomic | integer string | toujours | Seuil exact accepté dans la plus petite unité de l'actif. |
| received_amount | decimal string | toujours | Montant observé. |
| received_amount_atomic | integer string | toujours | Montant atomique observé. |
| confirmed_amount | decimal string | toujours | Montant confirmé/final. |
| confirmed_amount_atomic | integer string | toujours | Montant atomique confirmé/final. |
| destination_address | string | toujours | Adresse de réception on-chain, ou hash de paiement de 64 caractères pour Lightning. Utilise bolt11 pour payer via Lightning ; son hash n'est pas une adresse Bitcoin. |
| destination_tag | string | null | toujours | Référence publique de paiement obligatoire lorsque le réseau en utilise une : destination tag XRP, memo ID Stellar ou commentaire de facture TON. Null pour les réseaux à adresses uniques. |
| derivation_index | integer | toujours | Index enfant réservé du portefeuille ; uniquement dans le détail destiné au commerçant. |
| quote_expires_at | RFC 3339 timestamp | toujours | Expiration du devis. |
| monitoring_expires_at | RFC 3339 timestamp | toujours | Fin du suivi tardif pour ce moyen. |
| next_check_at | timestamp | null | toujours | Prochaine vérification programmée de la blockchain. |
| last_checked_at | timestamp | null | toujours | Dernière vérification de la blockchain. |
| last_chain_height | integer | null | toujours | Dernière hauteur fiable observée par le moniteur. |
| last_anchor_hash | string | null | toujours | Dernier hash d'ancrage/de bloc du moniteur. |
| last_monitor_error | string | null | toujours | Diagnostic de suivi sûr pour les opérateurs. |
| first_payment_at | timestamp | null | toujours | Heure de la première observation du paiement. |
| fully_paid_at | timestamp | null | toujours | Heure à laquelle le montant minimum accepté a été atteint pour la première fois. |
| finalized_at | timestamp | null | toujours | Heure à laquelle le paiement a satisfait la politique de finalité. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices/YOUR_PUBLIC_INVOICE_ID" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Accept: application/json'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices/YOUR_PUBLIC_INVOICE_ID", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
"Accept": "application/json"
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices/YOUR_PUBLIC_INVOICE_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Accept: application/json"],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Accept": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices/YOUR_PUBLIC_INVOICE_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"data": {
"id": "2f798f9f-01f2-42f0-9d10-5581d6116b4c",
"invoice_id": "0a6a98db-d93d-48ee-8c3c-fd45f90c4a50",
"project_id": "11111111-1111-4111-8111-111111111111",
"store_id": "22222222-2222-4222-8222-222222222222",
"source": "api",
"order_id": "order-1042",
"email": "ada@example.com",
"customer_name": "Ada Lovelace · Example GmbH",
"customer_address": "Example GmbH · 12 Example Street · Suite 2 · 10115 Berlin · Germany (DE) · VAT DE123456789",
"description": "Annual plan",
"amount": "49.9",
"currency": "USD",
"exchange_rate_spread_percent": "0.5",
"underpayment_tolerance_percent": "1",
"status": "settled",
"amount_status": "paid",
"timing_status": "on_time",
"resolution": "automatic",
"sequence": 4,
"winning_payment_intent_id": "33333333-3333-4333-8333-333333333333",
"expires_at": "2026-08-31T18:15:00Z",
"monitoring_expires_at": "2026-09-07T18:15:00Z",
"settled_at": "2026-08-31T18:05:00Z",
"cancelled_at": null,
"archived_at": null,
"created_at": "2026-08-31T18:00:00Z",
"updated_at": "2026-08-31T18:05:00Z",
"ipn_url": "https://merchant.example/wholly/ipn",
"redirect_url": "https://merchant.example/orders/1042",
"cancel_url": "https://merchant.example/cart",
"redirect_automatically": true,
"checkout_language": "en",
"metadata": { "cart_id": "cart-681", "firstname": "Ada", "lastname": "Lovelace", "street": "12 Example Street", "street2": "Suite 2", "zip": "10115", "city": "Berlin", "country": "Germany", "countryiso2": "DE", "company": "Example GmbH", "vatid": "DE123456789" },
"payment_intents": [
{
"id": "33333333-3333-4333-8333-333333333333",
"asset_id": "10000000-0000-4000-8000-000000000001",
"asset_key": "bip122:000000000019d6689c085ae165831e93/slip44:0",
"chain_slug": "bitcoin",
"network": "mainnet",
"caip_network_id": "bip122:000000000019d6689c085ae165831e93",
"caip_asset_id": "bip122:000000000019d6689c085ae165831e93/slip44:0",
"symbol": "BTC",
"asset_decimals": 8,
"status": "paid",
"finality_mode": "confirmations",
"required_confirmations": 1,
"quote_rate": "0.000009218",
"quote_details": null,
"expected_amount": "0.00046",
"expected_amount_atomic": "46000",
"minimum_payment_amount": "0.0004554",
"minimum_payment_amount_atomic": "45540",
"received_amount": "0.0004554",
"received_amount_atomic": "45540",
"confirmed_amount": "0.0004554",
"confirmed_amount_atomic": "45540",
"destination_address": "bc1q…example",
"destination_tag": null,
"derivation_index": 42,
"quote_expires_at": "2026-08-31T18:15:00Z",
"monitoring_expires_at": "2026-09-07T18:15:00Z",
"next_check_at": null,
"last_checked_at": "2026-08-31T18:05:00Z",
"last_chain_height": 912345,
"last_anchor_hash": "000000000000000000example",
"last_monitor_error": null,
"first_payment_at": "2026-08-31T18:03:00Z",
"fully_paid_at": "2026-08-31T18:03:00Z",
"finalized_at": "2026-08-31T18:05:00Z"
}
]
},
"links": {
"checkout": "https://pay.example.com/invoice/0a6a98db-d93d-48ee-8c3c-fd45f90c4a50"
}
}GETLister les paiements d'une facture/v1/projects/{project_id}/invoices/{invoice_id}/paymentsLecture seule
Historique complet et actuel des transferts, y compris les observations invalidées. Utilise-le lorsqu'un callback signale payments_truncated. Il s'agit de l'état actuel, pas d'une reconstitution d'un ancien événement.
- Une observation est un log de token, une sortie UTXO ou un autre transfert du réseau, pas nécessairement un hash de transaction unique. Déduplique par payment_id ; transaction_id avec event_index identifie le transfert sur la blockchain.
- status vaut detected, confirming, final, reorged, replaced ou invalid. Seules les observations counts_towards_received contribuent aux montants reçus. N'additionne jamais les montants d'actifs différents.
- Les enregistrements Lightning utilisent payment_hash avec transaction_id, les confirmations et les liens d'explorateur à null ; la précision BTC est de 11 (millisatoshis). Aucune préimage, BOLT11 ni aucun secret de portefeuille n'est exposé.
- Triés par observed_at décroissant, puis par payment_id décroissant. Le nombre et la page utilisent un même instantané à lecture répétable ; les pages suivantes peuvent changer à l'arrivée de paiements. Déduplique par payment_id lors de la pagination d'une facture active.
- Le périmètre de projet en lecture seule, les restrictions IP et les limites de fréquence par identifiant existants s'appliquent. Ne suis jamais un lien fourni par un callback avec ton token, sauf si son origine correspond à ton hôte API configuré.
| En-tête | Présence | Règle |
|---|---|---|
| Authorization | obligatoire | Bearer YOUR_MERCHANT_API_TOKEN |
| Accept | recommandé | application/json |
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| project_id | path UUID | Projet attribué à cet identifiant. |
| invoice_id | path UUID | invoice_id public renvoyé à la création. |
| payment_method_id | optional query UUID | Limite à un seul moyen de paiement de la facture. |
| limit | query integer | 1–100 ; valeur par défaut 25. |
| offset | query integer | 0–1 000 000 ; valeur par défaut 0. |
Requête
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices/YOUR_PUBLIC_INVOICE_ID/payments?limit=25&offset=0" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Accept: application/json'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices/YOUR_PUBLIC_INVOICE_ID/payments?limit=25&offset=0", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
"Accept": "application/json"
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices/YOUR_PUBLIC_INVOICE_ID/payments?limit=25&offset=0");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Accept: application/json"],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Accept": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/invoices/YOUR_PUBLIC_INVOICE_ID/payments?limit=25&offset=0",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"invoice_id": "11111111-2222-4333-8444-555555555555",
"data": [{
"payment_id": "44444444-4444-4444-8444-444444444444",
"payment_method_id": "33333333-3333-4333-8333-333333333333",
"transaction_id": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"payment_hash": null,
"event_index": 12,
"payment_rail": "onchain",
"chain_slug": "ethereum",
"network": "mainnet",
"asset_id": "55555555-5555-4555-8555-555555555555",
"asset_key": "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"caip_asset_id": "eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"symbol": "USDC",
"asset_decimals": 6,
"amount": "58.17342",
"amount_atomic": "58173420",
"status": "final",
"counts_towards_received": true,
"confirmations": 2,
"block_height": 25975377,
"observed_at": "2026-09-14T12:03:00Z",
"chain_time": "2026-09-14T12:02:48Z",
"finalized_at": "2026-09-14T12:04:00Z",
"explorer_name": "Etherscan",
"explorer_url": "https://etherscan.io/tx/0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}],
"pagination": {"limit": 25, "offset": 0, "total": 1, "has_more": false}
}GETStructure de la page de paiement/Public
Racine de l'hôte de paiement géré qui sert l'application de paiement sans sélectionner de facture. Les intégrations destinées aux clients devraient normalement utiliser links.checkout.
- Aucun jeton bearer n'est nécessaire.
- Le point d'accès du paiement géré autorise GET/HEAD et refuse les autres méthodes.
Requête
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/" \
--output 'checkout.html'// Node.js 18+ · run on your server, never in browser code.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://pay.example.com/", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
await writeFile("checkout.html", Buffer.from(await response.arrayBuffer()));<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
file_put_contents("checkout.html", $response);# Python 3 · standard library; run on your server.
import json
from pathlib import Path
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://pay.example.com/",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
Path("checkout.html").write_bytes(response.read())Exemple de réponse · 200 text/html
<!doctype html>
<!-- Hosted Wholly Crypto checkout shell -->GETPage de paiement hébergée/invoice/{invoice_id}Public
Page de paiement HTML destinée aux clients. La page récupère un JSON sûr pour le paiement depuis le même hôte. L'intégration dans une page est refusée, sauf si le magasin l'active et autorise explicitement l'origine HTTPS parente.
- Aucun token bearer n'est accepté ni nécessaire.
- La structure HTML renvoie 200 même si la facture est absente ; sa requête JSON de paiement reçoit ensuite invoice_not_found.
- La réponse est no-store, noindex et possède une CSP frame-ancestors propre à la facture.
- Un projet/magasin désactivé ou une facture inconnue n'expose aucune donnée de paiement.
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| invoice_id | path UUID | UUID public de facture renvoyé par l'API commerçant. |
Requête
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/invoice/YOUR_PUBLIC_INVOICE_ID" \
--output 'checkout.html'// Node.js 18+ · run on your server, never in browser code.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://pay.example.com/invoice/YOUR_PUBLIC_INVOICE_ID", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
await writeFile("checkout.html", Buffer.from(await response.arrayBuffer()));<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/invoice/YOUR_PUBLIC_INVOICE_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
file_put_contents("checkout.html", $response);# Python 3 · standard library; run on your server.
import json
from pathlib import Path
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://pay.example.com/invoice/YOUR_PUBLIC_INVOICE_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
Path("checkout.html").write_bytes(response.read())Exemple de réponse · 200 text/html
<!doctype html>
<!-- Hosted Wholly Crypto checkout application -->GETFacture avec données adaptées au paiement public/checkout-api/invoices/{invoice_id}Public
Renvoie uniquement les champs nécessaires à l'affichage du paiement. Omet volontairement les ID internes, l'email du client et les champs d'adresse dérivés, l'URL IPN, les métadonnées du commerçant, les ID de portefeuilles, les chemins de dérivation et les diagnostics du moniteur.
- Aucun jeton bearer n'est nécessaire.
- Cache-Control vaut no-store et l'indexation par les moteurs de recherche est désactivée.
- Traite invoice_id comme une donnée donnant accès au client ; évite de la publier inutilement.
- asset_icon_url est une ressource locale de même origine ; la page de paiement du client n'a jamais besoin de contacter CoinGecko pour l'afficher.
- Lorsque destination_tag n'est pas null, affiche-le et copie-le à côté de l'adresse : c'est un destination tag XRP, memo ID Stellar ou commentaire de facture TON obligatoire, à envoyer exactement tel quel.
- Pour les tokens vérifiés, asset_kind vaut token, contract_address identifie le contrat ERC-20 ou le mint SPL exact, token_standard identifie le réseau et payment_uri contient cette identité du token.
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| invoice_id | path UUID | UUID public de la facture. |
Facture publique de paiement
| Champ | Type | Présence | Description |
|---|---|---|---|
| invoice_id | UUID | toujours | UUID public de la facture. |
| order_id | string | null | toujours | Référence de commande du commerçant. |
| description | string | null | toujours | Description visible par le client. |
| amount | decimal string | toujours | Montant de la facture. |
| currency | string | toujours | Devise de la facture. |
| exchange_rate_spread_percent | decimal string | toujours | Marge effective du devis figée à la création, y compris une éventuelle valeur propre à la facture. |
| underpayment_tolerance_percent | decimal string | toujours | Pourcentage de manque accepté pour cette facture. |
| status | invoice status | toujours | Statut actuel de la facture. |
| amount_status | amount status | toujours | none, partial, paid ou overpaid. Une facture de montant nul explicitement autorisée est réglée avec none et sans moyen de paiement. |
| timing_status | timing status | toujours | on_time ou late. |
| sequence | integer | toujours | Séquence de l'état actuel. |
| active_payment_method_id | UUID | null | toujours | Le moyen de paiement de la liste qui a reçu des fonds. Le paiement reste sur ce moyen pour éviter de compléter un paiement insuffisant avec un actif incompatible. |
| payment_method_locked | boolean | toujours | True après qu'un paiement valide sélectionne active_payment_method_id. |
| server_time | RFC 3339 timestamp | toujours | Heure du serveur relevée pour cette réponse ; utilise-la avec expires_at pour éviter les écarts d'horloge de l'appareil du client. |
| expires_at | RFC 3339 timestamp | toujours | Échéance de la facture. |
| expires_in_seconds | integer | toujours | Secondes entières restantes à server_time, arrondies au supérieur et limitées à un minimum de zéro. |
| payment_open | boolean | toujours | True uniquement lorsqu'une facture new ou processing n'a pas encore atteint son échéance et possède au moins un moyen payable avec un montant restant. |
| redirect_url | string | null | toujours | Destination de retour du client après un règlement réussi. |
| cancel_url | string | null | toujours | Destination de retour du client lorsqu'il quitte sans règlement réussi. |
| redirect_automatically | boolean | toujours | Politique de redirection automatique. |
| checkout_language | string | toujours | Langue de la page de paiement. |
| project | object | toujours | name, checkout_title, checkout_description, theme, accent_color et logo_url. |
| store | object | toujours | Nom public du magasin. |
| appearance | CheckoutAppearance | toujours | Présentation effective : valeur propre à la facture figée si elle est fournie, sinon design actuel du magasin. Ne modifie jamais les champs financiers ni les avertissements de sécurité. |
| payment_methods | CheckoutPaymentMethod[] | toujours | Moyens de paiement sûrs pour la page de paiement. |
CheckoutAppearance
| Champ | Type | Présence | Description |
|---|---|---|---|
| inherit_default_store | boolean | toujours | True lorsque l'apparence provient du magasin par défaut du projet. False pour les magasins indépendants et les valeurs propres aux factures figées. |
| invoice_override | boolean | toujours | True lorsque checkout_appearance a été fourni à la création de la facture. Si omis/null, reste false. |
| title / intro / outro | string | toujours | Titre du commerçant, message supérieur et message inférieur en texte brut. intro remplace customer_message ; l'ancien texte enregistré est conservé. Ne jamais les interpréter comme du balisage. |
| intro_font_size / outro_font_size | integer | toujours | Tailles de police en pixels : 12, 14, 16, 18, 20 ou 24. |
| customer_message | string | toujours | Alias de compatibilité obsolète d'intro. Utilise intro pour les nouvelles intégrations. |
| theme | system | light | dim | dark | toujours | Préférence de l'appareil du client ou thème fixe. |
| accent_color / background_color / card_color / button_color | string | toujours | Couleurs strictement #RRGGBB. Les couleurs facultatives sont vides pour les valeurs automatiques ; le contraste du premier plan est calculé. |
| logo_size / logo_alignment | string | toujours | small, medium ou large ; left ou center. Les images sont contenues, pas recadrées. |
| images | object | toujours | URL facultatives logo_light, logo_dark et favicon : images PNG normalisées de même origine, limitées au périmètre autorisé. |
| show_order_id / show_description / details_expanded | boolean | toujours | Visibilité de l'ID de commande, description sous le titre et dépliage initial de l'ID de commande. Le montant reste visible ; il s'agit de contrôles d'affichage, pas de masquage des données. |
| show_project_name / show_store_name | boolean | toujours | Merchant 5.6.0+ : visibilité du nom dans l'en-tête. Les deux valent true par défaut. L'identité du projet/magasin reste disponible dans le JSON. |
| featured_chains / featured_asset_ids | array | toujours | Préférences ordonnées, appliquées uniquement aux moyens déjà présents dans la facture. Les moyens absents ou désactivés sont ignorés. |
| default_asset_id | UUID | null | toujours | Moyen initial suggéré. Une préférence valide mémorisée du client ou un moyen recevant déjà des fonds est prioritaire. |
| messages | object | toujours | Texte brut en/de avec les clés waiting, confirming, paid, underpaid et expired. Repli en anglais. Complémentaire ; ne remplace jamais le statut réel. |
| support_email / support_url / terms_url / privacy_url | string | toujours | Contact et liens HTTPS facultatifs, sans identifiants dans les URL. Les liens externes s'ouvrent dans une nouvelle fenêtre. |
| return_button_text | string | toujours | Libellé facultatif uniquement. Les destinations de succès/d'annulation et la politique de redirection restent propres à la facture. |
CheckoutPaymentMethod
| Champ | Type | Présence | Description |
|---|---|---|---|
| payment_rail | onchain | lightning | toujours | Lightning reste un moyen Bitcoin, distinct du BTC on-chain. Identifie le choix par l'id de l'intention et le réseau, pas seulement par asset_id. |
| bolt11 | string | null | toujours | Demande Lightning signée ; null pour les moyens on-chain. Ne paie jamais après que payable devient false. |
| payment_hash | string | null | toujours | Hash de paiement Lightning pour le rapprochement, pas une adresse de réception. Null pour les moyens on-chain. |
| id | UUID | toujours | Identifiant de l'intention de paiement. |
| asset_id | UUID | toujours | UUID de l'actif utilisé par les préférences d'apparence ; distinct de l'id de l'intention de paiement de cette facture. |
| asset_key | string | toujours | Clé canonique de l'actif. |
| chain_slug / chain_name | string | toujours | Noms de la blockchain pour la machine et l'affichage. |
| network | string | toujours | Réseau de paiement. |
| caip_network_id | string | toujours | Identité canonique du réseau utilisée pour identifier sans ambiguïté la blockchain sélectionnée. |
| caip_asset_id | string | null | toujours | Identité canonique exacte de l'actif, y compris un contrat ou mint de token vérifié le cas échéant. |
| asset_name / symbol | string | toujours | Valeurs d'affichage de l'actif de paiement. |
| asset_icon_url | string | null | toujours | Icône de l'actif de même origine, mise en cache localement, ou null si aucune correspondance CoinGecko vérifiée n'existe. |
| asset_kind | native | token | toujours | Distingue la monnaie native du paiement par contrat/mint. |
| contract_address | string | null | toujours | Contrat ERC-20 ou mint SPL canonique pour les tokens ; null pour la monnaie native. |
| token_standard | erc20 | spl-token | null | toujours | Environnement d'exécution vérifié du token, ou null pour la monnaie native. |
| asset_decimals | integer | toujours | Précision de l'unité atomique : 11 pour les millisatoshis BTC Lightning, 8 pour les satoshis BTC on-chain. |
| status | intent status | toujours | Statut actuel du moyen de paiement. |
| payable | boolean | toujours | True uniquement lorsque ce moyen précis peut actuellement accepter un paiement ; false pour les moyens inactifs après qu'un autre actif a reçu des fonds. |
| finality_mode / required_confirmations | string / integer | toujours | Politique de finalité. |
| expected_amount / expected_amount_atomic | decimal / integer string | toujours | Devis complet figé, en unités d'affichage et unités on-chain réelles. Les stablecoins fiat reconnus utilisent au plus deux décimales de devis, toujours arrondies au supérieur après la marge ; les autres actifs utilisent une précision adaptative. Les décimales réelles du token, les fonds reçus et les restes de paiements partiels restent exacts. Utilise les montants renvoyés sans les modifier. |
| minimum_payment_amount / minimum_payment_amount_atomic | decimal / integer string | toujours | Seuil de règlement accepté après application de la tolérance aux paiements insuffisants. |
| received_amount / received_amount_atomic | decimal / integer string | toujours | Montant observé. |
| remaining_amount | decimal string | toujours | Montant d'affichage exact encore nécessaire pour atteindre le seuil accepté, limité à un minimum de zéro. |
| remaining_amount_atomic | integer string | toujours | Manque par rapport au seuil accepté en unités atomiques. Ce n'est pas le montant de paiement demandé : la tolérance concerne uniquement l'acceptation. |
| confirmed_amount / confirmed_amount_atomic | decimal / integer string | toujours | Montant confirmé/final. |
| destination_address / destination_tag | string / string|null | toujours | Destination on-chain et référence facultative. Pour Lightning, c'est le hash de paiement sans tag ; paie plutôt via bolt11/payment_uri. |
| quote_expires_at | RFC 3339 timestamp | toujours | Expiration du devis. |
| payment_uri | string | null | toujours | Demande adaptée à la blockchain : ERC-681, Solana Pay, URI natif ou lightning:<bolt11>. Les demandes contenant un montant utilisent le montant attendu complet moins les fonds reçus, jamais le seuil de tolérance. Null lorsque payable vaut false, y compris après l'acceptation d'un manque toléré. Le QR Lightning encode la demande Lightning complète, pas le hash de paiement. |
| qr_url | path | null | toujours | Chemin QR SVG de même origine avec révision fondée sur la séquence et le reste exact, ou null lorsque payable vaut false. Le SVG est no-store. |
| address_explorer_name / address_explorer_url | string|null | toujours | Explorateur mainnet de repli validé lorsque pris en charge. |
| transaction_count | integer | toujours | Nombre total de transactions publiques, valides et distinctes observées pour ce moyen. |
| transactions_truncated | boolean | toujours | True lorsque transaction_count dépasse la liste de transactions récentes renvoyée. |
| transactions | CheckoutTransaction[] | toujours | Jusqu'aux 10 transactions publiques et valides les plus récentes. Les totaux reçus exacts restent indépendants de cette limite d'affichage. |
CheckoutTransaction
| Champ | Type | Présence | Description |
|---|---|---|---|
| transaction_id | string | toujours | Identifiant de la transaction observée. |
| status | detected | confirming | final | toujours | État public de l'observation. |
| confirmations | integer | toujours | Nombre de confirmations observé. |
| block_height | integer | null | toujours | Hauteur du bloc/registre observée. |
| explorer_name | string | si renvoyé | Nom fixe validé de l'explorateur. |
| explorer_url | string | si renvoyé | URL mainnet fixe validée de l'explorateur. |
Requête
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID" \
--header 'Accept: application/json'// Node.js 18+ · run on your server, never in browser code.
const response = await fetch("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID", {
method: "GET",
headers: {
"Accept": "application/json"
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ["Accept: application/json"],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Accept": "application/json"
}
request = Request("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"data": {
"invoice_id": "0a6a98db-d93d-48ee-8c3c-fd45f90c4a50",
"order_id": "order-1042",
"description": "Annual plan",
"amount": "49.9",
"currency": "USD",
"exchange_rate_spread_percent": "0.5",
"underpayment_tolerance_percent": "1",
"status": "processing",
"amount_status": "partial",
"timing_status": "on_time",
"sequence": 3,
"active_payment_method_id": "33333333-3333-4333-8333-333333333333",
"payment_method_locked": true,
"server_time": "2026-08-31T18:10:00Z",
"expires_at": "2026-08-31T18:15:00Z",
"expires_in_seconds": 300,
"payment_open": true,
"redirect_url": "https://merchant.example/orders/1042",
"cancel_url": "https://merchant.example/cart",
"redirect_automatically": true,
"checkout_language": "en",
"project": {
"name": "Example project",
"checkout_title": "Complete your payment",
"checkout_description": "Send the exact amount shown.",
"theme": "system",
"accent_color": "#42e39b",
"logo_url": "/checkout-api/invoices/…/logo/…/image.png"
},
"store": { "name": "Online shop" },
"payment_methods": [
{
"id": "33333333-3333-4333-8333-333333333333",
"asset_key": "bip122:000000000019d6689c085ae165831e93/slip44:0",
"chain_slug": "bitcoin",
"chain_name": "Bitcoin",
"network": "mainnet",
"caip_network_id": "bip122:000000000019d6689c085ae165831e93",
"caip_asset_id": "bip122:000000000019d6689c085ae165831e93/slip44:0",
"asset_name": "Bitcoin",
"symbol": "BTC",
"asset_icon_url": "/assets/coingecko/bitcoin.png",
"asset_kind": "native",
"contract_address": null,
"token_standard": null,
"asset_decimals": 8,
"status": "partial",
"payable": true,
"finality_mode": "confirmations",
"required_confirmations": 1,
"expected_amount": "0.00046",
"expected_amount_atomic": "46000",
"minimum_payment_amount": "0.0004554",
"minimum_payment_amount_atomic": "45540",
"received_amount": "0.0002",
"received_amount_atomic": "20000",
"remaining_amount": "0.0002554",
"remaining_amount_atomic": "25540",
"confirmed_amount": "0",
"confirmed_amount_atomic": "0",
"destination_address": "bc1q…example",
"destination_tag": null,
"quote_expires_at": "2026-08-31T18:15:00Z",
"payment_uri": "bitcoin:bc1q…example?amount=0.00026",
"qr_url": "/checkout-api/invoices/…/payment-methods/…/qr.svg?sequence=3&amount_atomic=26000",
"address_explorer_name": "mempool.space",
"address_explorer_url": "https://mempool.space/address/…",
"transaction_count": 0,
"transactions_truncated": false,
"transactions": []
}
]
}
}GETAperçu de la page de paiement du magasin/invoice/preview/{project_id}Public
Affiche l'apparence enregistrée du magasin avec un montant illustratif et les métadonnées réelles des actifs acceptés. Passe entre les exemples waiting, confirming, paid, underpaid et expired sans créer de paiements.
- L'aperçu concerne uniquement l'identité visuelle et ne doit jamais être envoyé à un client comme demande de paiement.
- Aucune adresse de réception, aucun QR payable, aucune action de portefeuille, redirection ni interrogation des paiements. Les exemples ne modifient pas le statut réel de la facture.
- La réponse est no-store, noindex et ne peut pas être intégrée dans une page.
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| project_id | path UUID | UUID du projet copié dans le lien d'aperçu par la console authentifiée. |
| store_id | query UUID, optional | Magasin appartenant à ce projet. Omet pour utiliser son premier magasin/par défaut. |
| state | query string, optional | waiting, confirming, paid, underpaid ou expired. Illustration uniquement dans le navigateur. |
Requête
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/invoice/preview/YOUR_PROJECT_ID?store_id=YOUR_STORE_ID&state=confirming" \
--output 'checkout-preview.html'// Node.js 18+ · run on your server, never in browser code.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://pay.example.com/invoice/preview/YOUR_PROJECT_ID?store_id=YOUR_STORE_ID&state=confirming", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
await writeFile("checkout-preview.html", Buffer.from(await response.arrayBuffer()));<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/invoice/preview/YOUR_PROJECT_ID?store_id=YOUR_STORE_ID&state=confirming");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
file_put_contents("checkout-preview.html", $response);# Python 3 · standard library; run on your server.
import json
from pathlib import Path
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://pay.example.com/invoice/preview/YOUR_PROJECT_ID?store_id=YOUR_STORE_ID&state=confirming",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
Path("checkout-preview.html").write_bytes(response.read())Exemple de réponse · 200 text/html
<!doctype html>
<!-- Hosted branding preview; no invoice is created -->GETDonnées d'aperçu du paiement/checkout-api/previews/{project_id}Public
Renvoie l'apparence effective du magasin et les métadonnées sûres des actifs acceptés. payment_methods reste vide ; preview_methods ne contient aucune adresse de paiement, aucun devis ni aucune donnée privée de portefeuille.
- Aucun token bearer n'est accepté ni nécessaire.
- Aucune facture, destination, aucun portefeuille, aucune transaction, aucun IPN, webhook ni aucune métadonnée du commerçant n'est renvoyé.
- Utilise la console authentifiée pour obtenir le bon lien d'aperçu sur le domaine pay.
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| project_id | path UUID | UUID du projet provenant du lien d'aperçu de la console. |
| store_id | query UUID, optional | Doit appartenir à ce projet ; les ID non concordants renvoient 404. Les champs de requête inconnus sont refusés. |
CheckoutAppearance
| Champ | Type | Présence | Description |
|---|---|---|---|
| inherit_default_store | boolean | toujours | True lorsque l'apparence provient du magasin par défaut du projet. False pour les magasins indépendants et les valeurs propres aux factures figées. |
| invoice_override | boolean | toujours | True lorsque checkout_appearance a été fourni à la création de la facture. Si omis/null, reste false. |
| title / intro / outro | string | toujours | Titre du commerçant, message supérieur et message inférieur en texte brut. intro remplace customer_message ; l'ancien texte enregistré est conservé. Ne jamais les interpréter comme du balisage. |
| intro_font_size / outro_font_size | integer | toujours | Tailles de police en pixels : 12, 14, 16, 18, 20 ou 24. |
| customer_message | string | toujours | Alias de compatibilité obsolète d'intro. Utilise intro pour les nouvelles intégrations. |
| theme | system | light | dim | dark | toujours | Préférence de l'appareil du client ou thème fixe. |
| accent_color / background_color / card_color / button_color | string | toujours | Couleurs strictement #RRGGBB. Les couleurs facultatives sont vides pour les valeurs automatiques ; le contraste du premier plan est calculé. |
| logo_size / logo_alignment | string | toujours | small, medium ou large ; left ou center. Les images sont contenues, pas recadrées. |
| images | object | toujours | URL facultatives logo_light, logo_dark et favicon : images PNG normalisées de même origine, limitées au périmètre autorisé. |
| show_order_id / show_description / details_expanded | boolean | toujours | Visibilité de l'ID de commande, description sous le titre et dépliage initial de l'ID de commande. Le montant reste visible ; il s'agit de contrôles d'affichage, pas de masquage des données. |
| show_project_name / show_store_name | boolean | toujours | Merchant 5.6.0+ : visibilité du nom dans l'en-tête. Les deux valent true par défaut. L'identité du projet/magasin reste disponible dans le JSON. |
| featured_chains / featured_asset_ids | array | toujours | Préférences ordonnées, appliquées uniquement aux moyens déjà présents dans la facture. Les moyens absents ou désactivés sont ignorés. |
| default_asset_id | UUID | null | toujours | Moyen initial suggéré. Une préférence valide mémorisée du client ou un moyen recevant déjà des fonds est prioritaire. |
| messages | object | toujours | Texte brut en/de avec les clés waiting, confirming, paid, underpaid et expired. Repli en anglais. Complémentaire ; ne remplace jamais le statut réel. |
| support_email / support_url / terms_url / privacy_url | string | toujours | Contact et liens HTTPS facultatifs, sans identifiants dans les URL. Les liens externes s'ouvrent dans une nouvelle fenêtre. |
| return_button_text | string | toujours | Libellé facultatif uniquement. Les destinations de succès/d'annulation et la politique de redirection restent propres à la facture. |
Requête
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID?store_id=YOUR_STORE_ID" \
--header 'Accept: application/json'// Node.js 18+ · run on your server, never in browser code.
const response = await fetch("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID?store_id=YOUR_STORE_ID", {
method: "GET",
headers: {
"Accept": "application/json"
},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID?store_id=YOUR_STORE_ID");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ["Accept: application/json"],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {
"Accept": "application/json"
}
request = Request("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID?store_id=YOUR_STORE_ID",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))Exemple de réponse · 200 application/json
{
"data": {
"preview": true,
"invoice_id": "YOUR_PROJECT_ID",
"amount": "100.00",
"currency": "USD",
"project": {
"name": "Example project",
"checkout_title": "Complete your payment",
"checkout_description": "Choose a network and send the exact amount shown.",
"theme": "system",
"accent_color": "#42e39b",
"logo_url": "/checkout-api/previews/…/logo/…/image.png"
},
"appearance": {"inherit_default_store": true, "theme": "system", "accent_color": "#42E39B", "images": {}},
"preview_methods": [],
"payment_methods": []
}
}GETImage de paiement du magasin/checkout-api/invoices/{invoice_id}/appearance-images/{kind}/{revision}/image.pngPublic
Renvoie un logo ou favicon normalisé du magasin appartenant à cette facture. Utilise les URL appearance.images des données de paiement.
- Utilise appearance.images du JSON de paiement. Les images figées de la facture continuent de fonctionner après que leur magasin source remplace ou supprime un fichier importé. Les révisions explicitement supprimées, associées à la mauvaise facture, au mauvais type ou inconnues renvoient 404 ; un instantané ne se rabat jamais sur l'image actuelle du magasin.
- Sans valeur propre à la facture, l'image effective actuelle du magasin est utilisée et les révisions remplacées/supprimées renvoient 404. PNG uniquement, nosniff et cache privé.
- L'importation d'images du magasin accepte des PNG, JPEG ou WebP dans les limites prévues, dans la console authentifiée ; jamais de SVG, HTML ni d'URL d'images distantes.
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| invoice_id | path UUID | UUID public de la facture. |
| kind | path enum | logo_light, logo_dark ou favicon. |
| revision | path UUID | Révision actuelle de l'image. |
Requête
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/appearance-images/logo_light/YOUR_IMAGE_REVISION/image.png" \
--output 'store-logo.png'// Node.js 18+ · run on your server, never in browser code.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/appearance-images/logo_light/YOUR_IMAGE_REVISION/image.png", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
await writeFile("store-logo.png", Buffer.from(await response.arrayBuffer()));<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/appearance-images/logo_light/YOUR_IMAGE_REVISION/image.png");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
file_put_contents("store-logo.png", $response);# Python 3 · standard library; run on your server.
import json
from pathlib import Path
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/appearance-images/logo_light/YOUR_IMAGE_REVISION/image.png",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
Path("store-logo.png").write_bytes(response.read())Exemple de réponse · 200 image/png
(binary PNG response)GETImage d'aperçu du magasin/checkout-api/previews/{project_id}/stores/{store_id}/appearance-images/{kind}/{revision}/image.pngPublic
Renvoie une image d'aperçu normalisée uniquement pour le projet, le magasin, le type et la révision actuelle correspondants.
- Utilise appearance.images des données d'aperçu. Les ID inconnus ou non concordants renvoient 404. Aucune information de portefeuille ou de paiement n'est exposée.
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| project_id | path UUID | UUID du projet. |
| store_id | path UUID | Magasin appartenant au projet. |
| kind | path enum | logo_light, logo_dark ou favicon. |
| revision | path UUID | Révision actuelle de l'image. |
Requête
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/appearance-images/logo_light/YOUR_IMAGE_REVISION/image.png" \
--output 'store-preview-logo.png'// Node.js 18+ · run on your server, never in browser code.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/appearance-images/logo_light/YOUR_IMAGE_REVISION/image.png", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
await writeFile("store-preview-logo.png", Buffer.from(await response.arrayBuffer()));<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/appearance-images/logo_light/YOUR_IMAGE_REVISION/image.png");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
file_put_contents("store-preview-logo.png", $response);# Python 3 · standard library; run on your server.
import json
from pathlib import Path
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/appearance-images/logo_light/YOUR_IMAGE_REVISION/image.png",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
Path("store-preview-logo.png").write_bytes(response.read())Exemple de réponse · 200 image/png
(binary PNG response)GETLogo d'aperçu avec révision/checkout-api/previews/{project_id}/logo/{revision}/image.pngPublic
Renvoie le logo normalisé du projet uniquement lorsque le projet et la révision du logo sûre pour le cache correspondent. Utilise project.logo_url des données d'aperçu au lieu de construire cette URL.
- Les projets inconnus et les révisions obsolètes du logo renvoient invoice_not_found sans révéler quel composant était absent.
- L'image avec révision renvoyée avec succès est immuable et peut être mise en cache.
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| project_id | path UUID | UUID du projet. |
| revision | path UUID | Révision actuelle du logo de paiement renvoyée dans project.logo_url. |
Requête
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID/logo/YOUR_LOGO_REVISION/image.png" \
--output 'checkout-preview-logo.png'// Node.js 18+ · run on your server, never in browser code.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID/logo/YOUR_LOGO_REVISION/image.png", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
await writeFile("checkout-preview-logo.png", Buffer.from(await response.arrayBuffer()));<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID/logo/YOUR_LOGO_REVISION/image.png");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
file_put_contents("checkout-preview-logo.png", $response);# Python 3 · standard library; run on your server.
import json
from pathlib import Path
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://pay.example.com/checkout-api/previews/YOUR_PROJECT_ID/logo/YOUR_LOGO_REVISION/image.png",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
Path("checkout-preview-logo.png").write_bytes(response.read())Exemple de réponse · 200 image/png
(binary PNG response)GETImage QR de paiement/checkout-api/invoices/{invoice_id}/payment-methods/{intent_id}/qr.svgPublic
Génère un QR SVG de 512×512 pour le contenu exact de paiement adapté à la blockchain d'un moyen de paiement de la facture.
- Aucun jeton bearer n'est nécessaire.
- Utilise qr_url avec révision fondée sur la séquence et le reste, renvoyé par le JSON de paiement ; le SVG est privé et no-store.
- Après un paiement partiel, il demande le montant restant exact et reste verrouillé sur cet actif.
- Renvoie 409 après l'expiration, la finalisation ou lorsqu'un autre moyen est actif ; renvoie payment_qr_unavailable (422) si la demande est trop grande pour être encodée.
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| invoice_id | path UUID | UUID public de la facture. |
| intent_id | path UUID | id du moyen de paiement provenant du JSON de paiement. |
Requête
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/payment-methods/YOUR_INTENT_ID/qr.svg" \
--output 'payment-qr.svg'// Node.js 18+ · run on your server, never in browser code.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/payment-methods/YOUR_INTENT_ID/qr.svg", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
await writeFile("payment-qr.svg", Buffer.from(await response.arrayBuffer()));<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/payment-methods/YOUR_INTENT_ID/qr.svg");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
file_put_contents("payment-qr.svg", $response);# Python 3 · standard library; run on your server.
import json
from pathlib import Path
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/payment-methods/YOUR_INTENT_ID/qr.svg",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
Path("payment-qr.svg").write_bytes(response.read())Exemple de réponse · 200 image/svg+xml
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">…</svg>GETLogo de paiement avec révision/checkout-api/invoices/{invoice_id}/logo/{revision}/image.pngPublic
Renvoie le logo de paiement normalisé du projet uniquement lorsque la facture et la révision actuelle du logo correspondent. Privilégie project.logo_url renvoyé par le JSON de paiement au lieu de construire cette route.
- Aucun jeton bearer n'est nécessaire.
- La durée du cache public est d'un an avec immutable, car la révision identifie l'état par son contenu.
- Les révisions inconnues/non concordantes renvoient invoice_not_found.
| Paramètre | Type / emplacement | Règle |
|---|---|---|
| invoice_id | path UUID | UUID public de la facture. |
| revision | path UUID | Révision actuelle du logo de paiement intégrée à project.logo_url. |
Requête
curl --fail-with-body --max-time 30 \
--request GET \
--url "https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/logo/YOUR_LOGO_REVISION/image.png" \
--output 'checkout-logo.png'// Node.js 18+ · run on your server, never in browser code.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/logo/YOUR_LOGO_REVISION/image.png", {
method: "GET",
headers: {},
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
await writeFile("checkout-logo.png", Buffer.from(await response.arrayBuffer()));<?php
// PHP 8+ with the cURL extension; run on your server.
$ch = curl_init("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/logo/YOUR_LOGO_REVISION/image.png");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
file_put_contents("checkout-logo.png", $response);# Python 3 · standard library; run on your server.
import json
from pathlib import Path
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
headers = {}
request = Request("https://pay.example.com/checkout-api/invoices/YOUR_PUBLIC_INVOICE_ID/logo/YOUR_LOGO_REVISION/image.png",
method="GET", headers=headers)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
Path("checkout-logo.png").write_bytes(response.read())Exemple de réponse · 200 image/png
(binary PNG response)Référence pour Wholly Crypto 7.5.5. Pour ta version installée, ouvre Réglages → Accès API → Documentation dans ta console. Voir les versions.