> ## 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.

# What is a Subscription?

> A subscription defines what blockchain addresses Off the Hook watches and where it delivers webhook notifications when a transfer is detected.

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

<ResponseField name="id" type="string" required>
  Unique identifier for the subscription, prefixed `sub_`. Use this ID in all subsequent API calls that reference the subscription.
</ResponseField>

<ResponseField name="destination" type="object" required>
  Where Off the Hook sends webhook deliveries.

  <Expandable title="destination fields">
    <ResponseField name="destination.type" type="string" required>
      Delivery mechanism. Currently only `"https"` is supported.
    </ResponseField>

    <ResponseField name="destination.url" type="string" required>
      The HTTPS endpoint that receives POST requests.
    </ResponseField>

    <ResponseField name="destination.secret" type="string">
      The signing secret used to verify webhook signatures. Off the Hook returns this **only at creation and after secret rotation** — it is never included in subsequent GET responses. Store it immediately and treat it like a password.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="events" type="string[]" required>
  The event kinds this subscription delivers. You must provide at least one. Example: `["wallet.transfer.broadcasted"]`. See [event types](/reference/event-types) for the full list.
</ResponseField>

<ResponseField name="filters.addresses" type="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.

  <Expandable title="address entry fields">
    <ResponseField name="chainId" type="string" required>
      CAIP-2 chain identifier (e.g. `"tron:mainnet"`). See [supported networks](/concepts/chains).
    </ResponseField>

    <ResponseField name="address" type="string" required>
      TRON base58check address (starts with `T`).
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="filters.hasMore" type="boolean">
  Present and `true` when the subscription has more than 200 watched addresses. Paginate the full list using `GET /v1/subscriptions/:id/filters/addresses`.
</ResponseField>

<ResponseField name="status" type="string" required>
  `"enabled"` or `"disabled"`. Disabled subscriptions do not receive any deliveries. Use this to pause monitoring without deleting your subscription.
</ResponseField>

<ResponseField name="description" type="string">
  An optional free-text label for your own reference. It has no effect on matching or delivery.
</ResponseField>

<ResponseField name="dateCreated" type="string" required>
  ISO 8601 timestamp of when the subscription was created.
</ResponseField>

<ResponseField name="dateUpdated" type="string" required>
  ISO 8601 timestamp of the most recent update to the subscription or its address filters.
</ResponseField>

## Example subscription

```json theme={null}
{
  "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"
}
```

<Note>
  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.
</Note>

## Address filters

Each entry in `filters.addresses` is scoped to a specific chain. The same base58check address on `tron:mainnet` and `tron:nile` is 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.

```bash theme={null}
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

* [Create a subscription](/api/subscriptions/create) — full API reference with all request parameters
* [Webhook events and payload structure](/concepts/events) — understand what your endpoint receives
