Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.offthehook.dev/llms.txt

Use this file to discover all available pages before exploring further.

A subscription is the core resource in Off the Hook. It ties together a destination URL, the event types you want to receive, and the address filters that tell the service which on-chain activity matters to you. When a transfer matches one of your watched addresses, Off the Hook constructs a signed webhook payload and delivers it to your destination. No subscription means no deliveries — everything flows from this single resource.

Key fields

id
string
required
Unique identifier for the subscription, prefixed sub_. Use this ID in all subsequent API calls that reference the subscription.
destination
object
required
Where Off the Hook sends webhook deliveries.
events
string[]
required
The event kinds this subscription delivers. You must provide at least one. Example: ["wallet.transfer.broadcasted"]. See event types for the full list.
filters.addresses
object[]
required
The blockchain addresses this subscription watches. Each entry pairs a chainId with an address. If this list is empty, no webhooks fire — Off the Hook has nothing to match against.
filters.hasMore
boolean
Present and true when the subscription has more than 200 watched addresses. Paginate the full list using GET /v1/subscriptions/:id/filters/addresses.
status
string
required
"enabled" or "disabled". Disabled subscriptions do not receive any deliveries. Use this to pause monitoring without deleting your subscription.
description
string
An optional free-text label for your own reference. It has no effect on matching or delivery.
dateCreated
string
required
ISO 8601 timestamp of when the subscription was created.
dateUpdated
string
required
ISO 8601 timestamp of the most recent update to the subscription or its address filters.

Example subscription

{
  "id": "sub_2QkP9aB7xN...",
  "destination": {
    "type": "https",
    "url": "https://your-server.example.com/webhooks"
  },
  "events": ["wallet.transfer.broadcasted"],
  "filters": {
    "addresses": [
      { "chainId": "tron:mainnet", "address": "TLsV52sRDL79HXGGm9yzwKibb6BeruhUzy" }
    ]
  },
  "status": "enabled",
  "description": "Production wallet monitor",
  "dateCreated": "2026-05-08T12:00:00Z",
  "dateUpdated": "2026-05-08T12:00:00Z"
}
The destination.secret field only appears in the response to POST /v1/subscriptions and POST /v1/subscriptions/:id/secrets/rotate. Copy it before leaving the page — there is no way to retrieve it again.

Address filters

Each entry in filters.addresses is scoped to a specific chain. The same base58check address on tron:mainnet and tron:nile are tracked independently — you need a separate entry for each chain you want to monitor. When you fetch a subscription with GET /v1/subscriptions/:id, the response inlines up to 200 addresses directly in the filters.addresses array. If you have more than 200, the response includes "hasMore": true and you retrieve the rest by paginating GET /v1/subscriptions/:id/filters/addresses using the nextPageToken cursor.

Idempotency

POST /v1/subscriptions accepts an Idempotency-Key header. If you send the same key twice, Off the Hook returns the original response without creating a duplicate subscription. This is useful when a network timeout leaves you uncertain whether the first request succeeded.
curl -X POST https://api.offthehook.dev/v1/subscriptions \
  -H "Authorization: Bearer oth_..." \
  -H "Idempotency-Key: my-unique-request-id-001" \
  -H "Content-Type: application/json" \
  -d '{
    "destination": { "type": "https", "url": "https://your-server.example.com/webhooks" },
    "events": ["wallet.transfer.broadcasted"],
    "status": "enabled"
  }'

Next steps