Abonnements WebSocket

Connectez-vous aux points de terminaison WebSocket de BlockVectra pour eth_subscribe newHeads et logs. Découvrez les modes de connexion, les règles de filtrage, le repli de reconnexion et la récupération.

BlockVectra fournit des connexions WebSocket sécurisées (wss://) pour diffuser en temps réel des abonnements aux événements Ethereum aux côtés des requêtes JSON-RPC standard.

Choisir entre WebSocket, Webhook ou polling

Utilisez WebSocket pour obtenir newHeads en direct et des logs filtrés lorsque votre application peut maintenir une connexion. Utilisez l'API Webhook blockchain pour recevoir l'activité des portefeuilles surveillés sur un point de terminaison HTTPS, avec vérification de signature sur le corps brut, nouvelles tentatives et rejeu des correspondances conservées. Utilisez le polling HTTP pour la surveillance planifiée des paiements ERC-20 et le rétro-remplissage des logs historiques. Le guide des stablecoins présente également un récepteur Webhook pour USDT / USDC. Pour une comparaison architecturale de la prise en charge des chaînes, des exigences du récepteur et des compromis de récupération pour les développeurs et les agents IA, consultez le guide pour choisir entre Webhooks, WebSocket ou polling RPC.

La prise en charge de WebSocket est issue des champs ws et subscriptions dans GET /v1/chains ; la prise en charge de Push provient de la liste authentifiée GET /v1/push/chains. Une chaîne sans support WebSocket peut néanmoins utiliser les Webhooks d'adresse si elle y est répertoriée.

Les déconnexions WebSocket nécessitent un réabonnement et un rétro-remplissage ; elles n'émettent pas les événements de contrôle Push subscription.gap ou chain.reorg. Pour les Webhooks, un écart nécessite un balayage de plage ; un avis de réorganisation impose de marquer ou d'ignorer les événements remplacés avant de conserver les événements canoniques automatiquement relivrés. Le rejeu Push renvoie les correspondances conservées, et non les données antérieures à l'ajout d'une adresse ou d'une chaîne ou pendant que l'abonnement était hors ligne. Consultez les règles de facturation et la référence des erreurs lors de la mise en œuvre de la récupération.

Chaînes disponibles

Vous pouvez vérifier si les abonnements WebSocket sont actifs sur un réseau en lisant ws (booléen) et subscriptions (tableau des types pris en charge) dans GET /v1/chains.

Le tableau ci-dessous indique les réseaux sur lesquels la prise en charge de WebSocket est activée :

ChaîneEndpoint WebSocket (clé dans le chemin)
Robinhood Chainwss://api.blockvectra.com/v1/robinhood_mainnet/{api_key}
Robinhood Chain Testnetwss://api.blockvectra.com/v1/robinhood_testnet/{api_key}

Connexion et authentification

Les clients établissent une connexion WebSocket TLS sécurisée (wss://). L'API key peut être fournie de deux manières :

  • Clé dans le chemin : wss://api.blockvectra.com/v1/{chain}/{api_key}
  • Clé dans l'en-tête : wss://api.blockvectra.com/v1/{chain} avec l'en-tête x-api-key: {api_key} ou Authorization: Bearer {api_key} lors du handshake de mise à niveau HTTP (Upgrade).

Avec une clé dans le chemin, cette dernière est utilisée et les deux en-têtes d'authentification sont ignorés. Sans clé dans le chemin, un en-tête x-api-key non vide prévaut sur Authorization: Bearer. Les API WebSocket des navigateurs ne peuvent pas définir ces en-têtes ; utilisez l'URL avec la clé dans le chemin.

Contrôles d'admission lors du handshake

Le handshake peut échouer pour les motifs suivants :

  • Authentification : l'absence d'API key renvoie HTTP 401 (missing_api_key) ; une API key inconnue, désactivée ou révoquée renvoie HTTP 401 (invalid_api_key) ; si l'authentification est temporairement indisponible, la réponse est HTTP 503 (auth_unavailable).
  • Solde du compte : un compte dont le solde prépayé est nul ou négatif renvoie HTTP 402 (balance_exhausted) ; si l'état de facturation ne peut pas être confirmé, la réponse est HTTP 503 (billing_unavailable).
  • Limites de connexion : le dépassement de la limite par clé (20 connexions) ou par compte (50 connexions) renvoie HTTP 429 (ws_connection_limit).
  • Disponibilité de la chaîne : demander une chaîne inconnue ou non desservie renvoie HTTP 404 (unknown_chain).
  • Capacité du serveur : lorsque le serveur est occupé ou surchargé, le handshake renvoie HTTP 503 (overloaded) avec un en-tête Retry-After.

Une fois connectés, les clients peuvent envoyer des requêtes JSON-RPC 2.0 standard (telles que eth_blockNumber ou eth_call) et des méthodes de contrôle d'abonnement formatées en trames de texte UTF-8.

Règles de facturation

  • L'établissement d'une connexion, le maintien d'une connexion inactive ouverte et les battements de cœur ping/pong ne sont pas facturés.
  • Les appels eth_subscribe et eth_unsubscribe réussis sont facturés, y compris un désabonnement renvoyant false ; les appels échoués ne sont pas facturés. Les appels JSON-RPC ordinaires suivent les règles de facturation JSON-RPC.
  • Les notifications newHeads comptent une seule fois par hash de bloc par connexion, quel que soit le nombre d'abonnements newHeads sur cette connexion.
  • Les notifications logs comptent une fois par abonnement par hash de bloc et phase contenant des logs correspondants ; les blocs sans correspondance ne sont pas facturés. Plusieurs logs correspondants dans le même bloc et la même phase ne multiplient pas les frais. Les abonnements distincts sont comptabilisés séparément, même lorsque leurs filtres se chevauchent. Les logs de réorganisation (removed: true) constituent une unité distincte ; un bloc de remplacement à la même hauteur a un hash différent et représente une unité différente.
  • Les notifications ne sont facturées qu'après avoir été vidées avec succès dans le tampon d'envoi du socket ; les notifications en file d'attente ou abandonnées qui n'ont pas été vidées ne sont pas facturées. Les notifications mises en file d'attente avant la réponse à un eth_unsubscribe sont facturées si elles ont été vidées. Les messages WebSocket ne comportent aucun en-tête de facturation HTTP ; consultez l'utilisation du compte pour connaître les CU mesurées.

Méthodes d'abonnement

L'API implémente l'interface pub/sub standard d'Ethereum : eth_subscribe et eth_unsubscribe.

newHeads

Émet un nouvel objet d'en-tête de bloc chaque fois qu'un nouveau bloc est ajouté au sommet de la chaîne.

  • Requête d'abonnement :
    {"jsonrpc":"2.0","id":1,"method":"eth_subscribe","params":["newHeads"]}
  • Réponse d'abonnement : renvoie un identifiant d'abonnement hexadécimal opaque :
    {"jsonrpc":"2.0","id":1,"result":"0x1"}
  • Trame de notification push :
    {"jsonrpc":"2.0","method":"eth_subscription","params":{"subscription":"0x1","result":{"number":"0x1b4","hash":"0x..."}}}

logs

Émet des événements de log correspondant aux critères de filtrage spécifiés.

  • Exigence de filtre : chaque filtre d'abonnement aux logs doit spécifier une address (une adresse de contrat ou un tableau d'adresses) ou un topic0 (la première position de topic, non nulle). Un filtre ne spécifiant ni l'un ni l'autre (comme {} ou {"topics":[null,"0x..."]}) est rejeté avec le code d'erreur -32602 (logs_filter_required).

  • Limites des filtres : 100 adresses au maximum ; au plus 4 positions de topics avec un maximum de 16 hashs candidats par position.

  • Capacité des filtres : si les filtres de logs actifs atteignent la capacité maximale, l'abonnement renvoie le code d'erreur -32022 (ws_filter_capacity).

  • Réorganisations de chaîne : si un bloc est supprimé en raison d'une réorganisation de chaîne, les notifications pour les logs supprimés comportent "removed": true.

  • Requête d'abonnement :

    {"jsonrpc":"2.0","id":2,"method":"eth_subscribe","params":["logs",{"address":"0x1234567890123456789012345678901234567890"}]}

eth_unsubscribe

Résilie un abonnement actif à l'aide de son identifiant d'abonnement.

  • Requête de désabonnement :
    {"jsonrpc":"2.0","id":3,"method":"eth_unsubscribe","params":["0x1"]}
  • Réponse de désabonnement :
    {"jsonrpc":"2.0","id":3,"result":true}

Exemples exécutables

Connectez-vous à l'aide de viem v2 via createPublicClient et le transport webSocket. Remplacez {chain} par l'identifiant de la chaîne cible et {api_key} par votre API key :

import { createPublicClient, webSocket } from 'viem';

const apiKey = process.env.BLOCKVECTRA_API_KEY || '{api_key}';
const url = `wss://api.blockvectra.com/v1/robinhood_mainnet/${apiKey}`;

const client = createPublicClient({
  transport: webSocket(url),
});

// 1. S'abonner aux nouveaux en-têtes de blocs (newHeads)
const unwatchBlocks = client.watchBlocks({
  onBlock: (block) => {
    console.log('New block header received:', block.number, block.hash);
  },
  onError: (error) => {
    console.error('watchBlocks error:', error);
  },
});

// 2. S'abonner aux logs d'événements de contrat (le filtre exige address ou topic0)
const unwatchEvents = client.watchEvent({
  address: '0x1234567890123456789012345678901234567890',
  onLogs: (logs) => {
    console.log('Matching logs received:', logs);
  },
  onError: (error) => {
    console.error('watchEvent error:', error);
  },
});

Codes de fermeture et actions du client

Lorsque le serveur met fin à une session WebSocket, il envoie une trame Close contenant un code de fermeture spécifique et un motif court. Le tableau ci-dessous liste les codes de fermeture émis par le serveur et les actions recommandées :

Code de fermetureChaîne du motifDescriptionRéessayableAction du client
1001idleConnexion inactive sans abonnements ni messages pendant 3 600 secondes (1 heure)OuiSe reconnecter selon les besoins.
1003binary frames are not acceptedTrame WebSocket binaire reçue ; trames de texte UTF-8 uniquementNonNe pas se reconnecter automatiquement. Mettre à jour le client pour envoyer des trames de texte.
1009message too largeLa charge utile entrante a dépassé 1 MioNonNe pas se reconnecter automatiquement. Découper les requêtes volumineuses ou réduire la taille de la charge utile.
1012service restartRedémarrage du serveur, ou durée de vie maximale de la session atteinte (24 heures)OuiSe reconnecter avec un délai aléatoire (jitter), rétablir les abonnements et rattraper les données manquées.
1013chain unavailableChaîne indisponibleOuiSe reconnecter avec un repli exponentiel aléatoire (full-jitter), rétablir les abonnements et rattraper les données manquées.
1013overloadedServeur temporairement surchargéOuiSe reconnecter avec un repli exponentiel aléatoire (full-jitter), rétablir les abonnements et rattraper les données manquées.
4402insufficient balanceSolde du compte épuiséNonNe pas se reconnecter automatiquement. Rechargez votre solde, puis reconnectez-vous.
4404invalid api keyL'API key est inconnue, désactivée ou révoquéeNonNe pas se reconnecter automatiquement. Vérifiez ou renouvelez votre API key dans la console avant de vous reconnecter.
4408slow consumerLe serveur ferme la session dont la file d'attente push dépasse 512 Kio et abandonne les notifications en attente ; les clients peuvent ne pas recevoir de trame Close (le navigateur signale 1006)OuiTraitez les déconnexions inattendues (aucune trame Close reçue, le navigateur signale 1006) comme le code 4408 : reconnectez-vous avec un délai, rétablissez les abonnements et rattrapez les données abandonnées avec eth_getLogs ; réduisez les abonnements ou lisez plus rapidement.
4429push rate exceededLe débit de notification a dépassé 1 000 pushs/secondeOuiRéduisez les abonnements ou affinez les filtres ; reconnectez-vous avec un délai, réabonnez-vous et effectuez un rattrapage.
4503billing unavailableFacturation temporairement indisponibleOuiÉtat transitoire ; reconnectez-vous avec un repli exponentiel aléatoire (full-jitter).

Reconnexion et repli exponentiel

Pour éviter les tempêtes de reconnexion synchronisées lors des coupures de connexion, les clients doivent mettre en œuvre un repli exponentiel avec gigue complète (full jitter) :

  • Formule de repli : avant la n-ième tentative de reconnexion (n = 0, 1, 2, ...), attendez une durée choisie uniformément au hasard :
    delay = random(0, min(20s, 0.5s * 2^n))
  • Réinitialisation du compteur : ne réinitialisez le compteur de tentatives n à 0 qu'après avoir maintenu une connexion ininterrompue et stable pendant au moins 60 secondes.
  • Code de fermeture 1012 : introduisez un délai initial aléatoire avant la première tentative de reconnexion pour éviter les pics de reconnexion synchronisés.
  • Codes non réessayables : ne vous reconnectez pas automatiquement lors des codes 4402, 4404, 1003 ou 1009.

Rétro-remplissage des données manquées après reconnexion

Les abonnements WebSocket ne persistent pas entre les connexions ; les notifications émises pendant une déconnexion ne sont pas conservées sur le serveur. Après reconnexion, les clients doivent appliquer une stratégie de rattrapage :

  1. Rétro-remplir les logs avec eth_getLogs :
    • Persistez le numéro de bloc le plus élevé traité avec succès (last_processed_block).
    • Appelez immédiatement eth_subscribe("logs", ...) dès la reconnexion pour capturer les événements en direct.
    • Interrogez les blocs manqués via eth_getLogs avec fromBlock: last_processed_block + 1 et toBlock: "latest" (ou le premier bloc reçu du flux en direct).
    • Si l'écart de déconnexion dépasse le max_logs_block_range du réseau (issu de GET /v1/chains), partitionnez les requêtes en segments ne dépassant pas cette limite.
    • Dédupliquez les entrées de logs à la frontière de la requête à l'aide du tuple unique (blockHash, transactionHash, logIndex).
  2. Rétro-remplir les en-têtes de blocs avec eth_getBlockByNumber :
    • Enregistrez le dernier numéro de bloc et son hash reçus avant la déconnexion.
    • Réabonnez-vous à newHeads.
    • Interrogez eth_getBlockByNumber("latest", false) et récupérez séquentiellement les blocs intermédiaires manquants. Vérifiez la continuité de la chaîne via parentHash pour détecter les réorganisations.

Limites

LimiteValeurRésultat en cas de dépassement
Abonnements par connexion WebSocket100-32022 subscription_limit
Abonnements newHeads par connexion WebSocket4-32022 subscription_limit
Exigences de filtre pour l'abonnement aux logsDoit spécifier une address ou un topic0 (première position dans topics)-32602 logs_filter_required

Prochaines étapes

Dernière mise à jour :

Sur cette page