Migrating from v1 to v2
Guide for migrating legacy v1 chainhooks to Chainhook 2.0 Beta
Managing v1 chainhooks
Legacy v1 chainhooks remain read-only in the Platform UI; you manage them through the Platform API.
What you'll learn
Capture and analyze all existing v1 chainhooks.
Convert predicate-based filters into explicit v2 event definitions.
Register v2 chainhooks, test delivery, and retire the originals safely.
Prerequisites
- API access to both the Platform API and Chainhook REST API (same
HIRO_API_KEY). - Local SDK (
@hirosystems/chainhooks-client) orcurlfor REST calls. - Environment variable
HIRO_API_KEYset for the CLI and code samples.
Inventory v1 chainhooks
Use the Platform API to fetch every chainhook that still fires in production.
CLI
curl -sS "https://api.platform.hiro.so/v1/ext/$HIRO_API_KEY/chainhooks" \-H "content-type: application/json"
TypeScript
export async function listV1Chainhooks() {const response = await fetch(`https://api.platform.hiro.so/v1/ext/${process.env.HIRO_API_KEY}/chainhooks`,{ headers: { 'content-type': 'application/json' } });if (!response.ok) throw new Error(response.statusText);return (await response.json()) as any[];}const chainhooks = await listV1Chainhooks();console.log(`Found ${chainhooks.length} v1 chainhooks`);
Inspect a chainhook
Pull the full definition for each UUID so you can convert custom filters and metadata.
CLI
curl -sS \"https://api.platform.hiro.so/v1/ext/$HIRO_API_KEY/chainhooks/$CHAINHOOK_UUID" \-H "content-type: application/json"
TypeScript
export async function getV1Chainhook(uuid: string) {const response = await fetch(`https://api.platform.hiro.so/v1/ext/${process.env.HIRO_API_KEY}/chainhooks/${uuid}`,{ headers: { 'content-type': 'application/json' } });if (!response.ok) throw new Error(response.statusText);return await response.json();}
Map configuration to v2
Translate v1 structures to v2 fields before provisioning new hooks.
| v1 Concept | v2 Target | Notes |
|---|---|---|
if_this.scope | filters.events[].type | Replace scope/action combos with a single event type. |
if_this.actions | type | transfer maps to *_transfer. |
then_that.http_post.url | action.url | v2 handles secrets automatically. |
networks.mainnet | network: "mainnet" | Create one v2 hook per network. |
authorization_header | Webhook secret management | Use rotateConsumerSecret() after registration. |
Example Conversion
// v1 (Platform API){"name": "stx-transfers","networks": {"mainnet": {"if_this": { "scope": "stx_event", "actions": ["transfer"] },"then_that": { "http_post": { "url": "https://example.com/webhooks" } }}}}
// v2 (SDK)import { ChainhooksClient, CHAINHOOKS_BASE_URL } from '@hirosystems/chainhooks-client';const client = new ChainhooksClient({baseUrl: CHAINHOOKS_BASE_URL.mainnet,apiKey: process.env.HIRO_API_KEY!,});await client.registerChainhook({version: '1',name: 'stx-transfers',chain: 'stacks',network: 'mainnet',filters: {events: [{ type: 'stx_transfer' }],},action: {type: 'http_post',url: 'https://example.com/webhooks',},options: {decode_clarity_values: true,enable_on_registration: true,},});
Create v2 chainhooks
Provision each chainhook with the SDK or REST API, mirroring the mapped filters.
REST
curl -sS -X POST "https://api.mainnet.hiro.so/chainhooks/v1/me/" \-H "content-type: application/json" \-H "x-api-key: $HIRO_API_KEY" \-d @v2-chainhook.json
Chainhook SDK
const client = new ChainhooksClient({baseUrl: CHAINHOOKS_BASE_URL[config.network],apiKey: process.env.HIRO_API_KEY!,});const chainhook = await client.registerChainhook(config);
Validate and retire v1
Stream events through both versions, confirm delivery, then clean up the legacy definitions.
Best practices
Keep both v1 and v2 hooks active until you verify delivery parity.
Enablement Checks
const chainhook = await client.getChainhook(v2Uuid);console.log(chainhook.status.enabled);
Delete with cURL
curl -sS -X DELETE \"https://api.platform.hiro.so/v1/ext/$HIRO_API_KEY/chainhooks/$CHAINHOOK_UUID" \-H "content-type: application/json"
Delete with SDK
export async function deleteV1Chainhook(uuid: string) {const response = await fetch(`https://api.platform.hiro.so/v1/ext/${process.env.HIRO_API_KEY}/chainhooks/${uuid}`,{ method: 'DELETE', headers: { 'content-type': 'application/json' } });if (!response.ok) throw new Error(response.statusText);return await response.json();}
Filter Translation Reference
Common scopes
| v1 | Typical Actions | v2 | Extras |
|---|---|---|---|
stx_event | transfer | stx_transfer | Include sender or recipient filters as needed. |
contract_call | n/a | contract_call | Add contract_identifier and function_name. |
ft_event | transfer | ft_transfer | Specify asset_identifier. |
nft_event | transfer, mint | nft_transfer · nft_mint | Provide asset_identifier. |
Example
// v1 filter{"scope": "stx_event","actions": ["transfer"]}
// v2 filter{"events": [{"type": "stx_transfer","recipient": "SP3FBR2AGKQX0..."}]}