Quick Start

Live Canton Network events over WebSocket. GraphQL subscriptions for transfers, ledger updates, traffic purchases, reward coupons and mining rounds

Streams push new Canton Network events to you as CC View indexes them. You open one WebSocket connection, subscribe to the streams you need with a filter, and receive events in batches as they are recorded on the ledger. No polling, no cursors to manage.

StreamWhat it emitsTypical use
transfersCreatedToken transfers, CC and CIP-56 tokensDeposit detection, wallet activity, venue volume
updatesCreatedLedger updates (committed transactions), public or privateTransaction monitoring, compliance, forensics
trafficPurchasedSynchronizer traffic purchasesValidator cost monitoring
couponsCreatedReward couponsReward tracking for app providers and beneficiaries
miningRoundsCreatedNew mining rounds with the CC price and fee scheduleCC/USD price per round, fee and issuance changes

Each stream has its own page with the filter, the event fields and worked examples.

Endpoint

ItemValue
WebSocketwss://ccview.io/graphql
ProtocolGraphQL over WebSocket, subprotocol graphql-transport-ws
Schema and playgroundccview.io/graphiql

The same path over HTTPS, https://ccview.io/graphql, answers healthCheck and schema introspection. Subscriptions run over WebSocket only.

Plans and API keys

Streams are included in the Growth and Enterprise plans. The key is the same one you use for the REST API. To get a key or move to a plan with streams, write to [email protected]. Plan details are on the pricing page.

Authentication

Pass your key in the x-api-key header of the request that opens the stream. Every new stream needs its own request with the header set:

x-api-key: YOUR_API_KEY

Client libraries let you set request headers when you create the connection: in gql, pass them to the transport's headers argument; with graphql-ws in Node, supply them through the WebSocket implementation. The examples below show both.

If the header is missing or the key is not accepted, the stream fails with a GraphQL error:

json {"errors": [ { "message": "API_KEY_NOT_FOUND", "locations": [{ "line": 3, "column": 3 }], "path": ["transfersCreated"] } ]}

The key is checked when the stream is created, so open a new stream with the correct x-api-key header to subscribe again.

Quick start

Subscribe to mainnet transfers and print every event. Set CCVIEW_API_KEY in your environment first.

import { createClient } from "graphql-ws";
import WebSocket from "ws";

const client = createClient({
  url: "wss://ccview.io/graphql",
  webSocketImpl: WebSocket,
  connectionParams: { 
    headers: { "x-api-key": process.env.CCVIEW_API_KEY } 
  }
});

const query = `
subscription Transfers {
  transfersCreated(filter: { network: "mainnet" }) {
    recordTime
    sender
    receiver
    amount
    tokenId
  }
}
`;

client.subscribe(
  { query },
  {
    next: ({ data }) => {
      for (const transfer of data.transfersCreated) console.log(transfer);
    },
    error: (err) => console.error("stream error", err),
    complete: () => console.log("stream closed"),
  },
);
import asyncio
import os

from gql import Client, gql
from gql.transport.websockets import WebsocketsTransport

transport = WebsocketsTransport(
    url="wss://ccview.io/graphql",
    subprotocols=[WebsocketsTransport.GRAPHQLWS_SUBPROTOCOL],
    init_payload={"headers": {"x-api-key": os.environ["CCVIEW_API_KEY"]}},
)

QUERY = gql("""
subscription Transfers {
  transfersCreated(filter: { network: "mainnet" }) {
    recordTime
    sender
    receiver
    amount
    tokenId
  }
}
""")

async def main():
    async with Client(transport=transport) as session:
        async for message in session.subscribe(QUERY):
            for transfer in message["transfersCreated"]:
                print(transfer)

asyncio.run(main())
# Connect with the graphql-transport-ws subprotocol, for example with wscat:
#   wscat -c wss://ccview.io/graphql -s graphql-transport-ws

> {"type":"connection_init","payload":{"headers":{"x-api-key":"YOUR_API_KEY"}}}
< {"type":"connection_ack"}

> {"id":"1","type":"subscribe","payload":{"query":"subscription { transfersCreated(filter: { network: \"mainnet\" }) { recordTime sender receiver amount tokenId } }"}}
< {"id":"1","type":"next","payload":{"data":{"transfersCreated":[{"recordTime":"2026-09-30T14:03:11.482913130Z","sender":"alice::1220a3f0…","receiver":"bob::1220c81d…","amount":"125.0000000000","tokenId":"cc"}]}}}

# Stop the stream:
> {"id":"1","type":"complete"}

Every next message carries an array. A busy stream delivers several events per message, a quiet one delivers one. Always iterate.

Filters

Every stream takes an optional filter argument. The rules are the same for all streams:

  • Every field is optional. Omit the whole filter to receive every event the stream produces.
  • Fields combine with AND. transfersCreated(filter: { network: "mainnet", tokenId: "cc" }) delivers CC transfers on mainnet and nothing else.
  • network is mainnet, testnet or devnet. Set it in every subscription unless you want events from all networks on one stream.
  • Party fields (sender, receiver, beneficiary, validator, participant) accept a party ID or its ANS name.
  • provider accepts the provider party ID or the app name as it appears in Featured Apps.
  • List fields (actingParties, instruments) match when any of the listed values matches.
  • A filter belongs to one subscription. Open several subscriptions on the same connection to watch different things.

Batches and ordering

Events that CC View indexes together arrive in one message, as one array. Within a batch, events are listed in ledger record-time order.

Delivery and reconnecting

Streams are live only. There is no replay and no cursor: events recorded while your connection is down are not redelivered when you reconnect.

To make this safe:

  1. Remember the recordTime of the last event you processed.
  2. On reconnect, subscribe first, then backfill the gap from the matching REST endpoint, paging from that time with its cursor parameter.
  3. Deduplicate on a stable key while the two overlap: updateId for updates, contractId for coupons, roundNumber for rounds, and the tuple (recordTime, sender, receiver, amount, tokenId) for transfers.
StreamBackfill with
transfersCreatedList token transfers
updatesCreatedUpdates list
trafficPurchasedList traffic purchases
couponsCreatedRewards history
miningRoundsCreatedRounds history

Keep the connection alive with the protocol's ping and pong messages. graphql-ws and gql answer pings for you. If your network closes idle connections, send your own pings as well: keepAlive in graphql-ws, ping_interval in gql.

Errors

MessageMeaningWhat to do
API_KEY_NOT_FOUNDNo x-api-key in the connection_init payload, or the key is unknownCheck the key and the payload shape, then reconnect

Validation errors (an unknown field, a wrong filter type) come back as standard GraphQL errors with the locations of the problem in your query.

Try it in the browser

Open GraphiQL, paste your key in the X-API-KEY field and press Run. The Docs panel on the left shows the full schema. The link below opens GraphiQL with the transfers subscription already filled in.

Open in GraphiQL ↗