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

# PUT /v1/subscriptions/:id

> Replace a subscription's destination URL, events, status, and description in a single call. Does not affect address filters or the signing secret.

Use this endpoint to replace all mutable fields on an existing subscription. Because this is a PUT, you must supply every field — any field you omit will be reset to its default value. Address filters and the signing secret are never affected by this operation; use the addresses endpoint to modify filters and the rotate endpoint to issue a new secret.

## Request

**`PUT /v1/subscriptions/:id`**

### Headers

| Header          | Required | Description               |
| --------------- | -------- | ------------------------- |
| `Authorization` | Yes      | `Bearer oth_YOUR_API_KEY` |
| `Content-Type`  | Yes      | `application/json`        |

### Path parameters

<ParamField path="id" type="string" required>
  The subscription ID to update. Always starts with `sub_`.
</ParamField>

### Body

<ParamField body="destination" type="object" required>
  Replacement webhook delivery target.

  <Expandable title="destination properties">
    <ParamField body="destination.type" type="string" required>
      Must be `"https"`.
    </ParamField>

    <ParamField body="destination.url" type="string" required>
      Replacement HTTPS URL. Must be publicly reachable and must not resolve to a private IP range. Validated at request time and again on each delivery.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="events" type="string[]" required>
  Replacement list of event kinds to receive. Must be non-empty.
</ParamField>

<ParamField body="status" type="string" required>
  `"enabled"` or `"disabled"`.
</ParamField>

<ParamField body="description" type="string" required>
  Human-readable label. Pass an empty string to clear it.
</ParamField>

### Example request

```bash theme={null}
curl -X PUT https://api.offthehook.dev/v1/subscriptions/sub_2QkP9aB7xN... \
  -H "Authorization: Bearer oth_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "destination": {
      "type": "https",
      "url": "https://your-server.example.com/webhooks-v2"
    },
    "events": ["wallet.transfer.broadcasted"],
    "status": "enabled",
    "description": "Updated description"
  }'
```

## Response

**`200 OK`**

Returns the updated subscription resource. The `destination.secret` field is not present in the response. The `filters.addresses` array will be empty in the response body (address filters are unaffected by this call — use `GET /v1/subscriptions/:id` to see current filters).

```json theme={null}
{
  "id": "sub_2QkP9aB7xN...",
  "destination": { "type": "https", "url": "https://your-server.example.com/webhooks-v2" },
  "events": ["wallet.transfer.broadcasted"],
  "filters": { "addresses": [] },
  "status": "enabled",
  "description": "Updated description",
  "dateCreated": "2026-05-08T12:00:00Z",
  "dateUpdated": "2026-05-08T12:01:00Z"
}
```

### Response fields

<ResponseField name="id" type="string" required>
  Unique subscription identifier, prefixed with `sub_`.
</ResponseField>

<ResponseField name="destination" type="object" required>
  Updated delivery target. `destination.secret` is not included.

  <Expandable title="destination properties">
    <ResponseField name="destination.type" type="string" required>
      Destination type. Currently always `"https"`.
    </ResponseField>

    <ResponseField name="destination.url" type="string" required>
      The updated HTTPS URL.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="events" type="string[]" required>
  The updated list of event kinds.
</ResponseField>

<ResponseField name="filters" type="object" required>
  Contains an empty `addresses` array. Your existing address filters remain in place — this endpoint does not modify them.
</ResponseField>

<ResponseField name="status" type="string" required>
  Updated status: `"enabled"` or `"disabled"`.
</ResponseField>

<ResponseField name="description" type="string" required>
  Updated human-readable label.
</ResponseField>

<ResponseField name="dateCreated" type="string" required>
  ISO 8601 creation timestamp. Unchanged by updates.
</ResponseField>

<ResponseField name="dateUpdated" type="string" required>
  ISO 8601 timestamp of this update.
</ResponseField>

## Errors

| Code                   | Description                                                                       |
| ---------------------- | --------------------------------------------------------------------------------- |
| `not_found`            | No subscription with the given ID exists under your API key.                      |
| `invalid_request`      | A required field is missing, contains an invalid value, or the JSON is malformed. |
| `ssrf_blocked`         | The new `destination.url` resolved to a private or reserved IP range.             |
| `unsupported_event`    | One or more entries in `events` are not recognized.                               |
| `description_too_long` | The `description` field exceeds the maximum allowed length.                       |
| `unauthorized`         | The `Authorization` header is missing or the API key is invalid.                  |
