> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ionicfi.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Manage endpoints

> Register, update, rotate, and disable webhook endpoints from the API.

A webhook endpoint is a URL plus the set of event types it should receive. Manage endpoints from the API with a secret key, or from the dashboard. Endpoints are scoped to one mode — a `sk_v1_test_…` key manages test-mode endpoints, a live key manages live ones.

On Node, `npm install @ionicfi/sdk` gives you typed calls for every request
below; the [server SDK](/sdks/server) guide has details. Each example also
shows raw curl.

Every write on this API (create, update, delete, rotate) requires an
`Idempotency-Key` header; the SDK types it as a required field on those
calls.

## Create an endpoint

<CodeGroup>
  ```ts TypeScript theme={null}
  import { Ionic } from "@ionicfi/sdk";

  const ionic = new Ionic({ token: process.env.IONIC_SECRET_KEY });

  const endpoint = await ionic.webhookEndpoints.create({
    "Idempotency-Key": "register-fulfillment-endpoint",
    url: "https://example.com/webhooks/ionic",
    event_types: ["payment_intent.succeeded", "refund.succeeded"],
    description: "Order fulfillment",
  });

  console.log(endpoint.secret); // whsec_..., shown only in this response
  ```

  ```bash curl theme={null}
  curl https://api.ionicfi.com/v1/webhook_endpoints \
    -H "Authorization: Bearer sk_v1_test_..." \
    -H "Idempotency-Key: register-fulfillment-endpoint" \
    -H "Content-Type: application/json" \
    -d '{
      "url": "https://example.com/webhooks/ionic",
      "event_types": ["payment_intent.succeeded", "refund.succeeded"],
      "description": "Order fulfillment"
    }'
  ```
</CodeGroup>

| Field         | Required | Description                                                                |
| ------------- | -------- | -------------------------------------------------------------------------- |
| `url`         | yes      | HTTPS URL Ionic delivers to.                                               |
| `event_types` | yes      | Which events to receive. See [filtering](#filtering-events) for wildcards. |
| `description` | no       | A label for your own reference.                                            |
| `api_version` | no       | Snapshot version to pin. Defaults to the current version (`2026-05-01`).   |

The response is the endpoint object. The `secret` is present **only** in this create response and in a [rotate](#rotate-the-signing-secret) response — store it now; it's never returned again.

```json theme={null}
{
  "id": "whe_0J8kP2mNq7rVxYzAbCdEfGhI",
  "object": "webhook_endpoint",
  "merchant_id": "mer_8Qd2...",
  "livemode": false,
  "url": "https://example.com/webhooks/ionic",
  "description": "Order fulfillment",
  "event_types": ["payment_intent.succeeded", "refund.succeeded"],
  "api_version": "2026-05-01",
  "status": "active",
  "disabled": false,
  "secret": "whsec_MfKQ9r8GKYqrTwjUPD8ILPZ...",
  "created_at": 1746122645,
  "updated_at": 1746122645
}
```

<Warning>
  `secret` appears once. If you lose it, you can't read it back — [rotate](#rotate-the-signing-secret) to get a new one. Ionic stores only what's needed to verify; it never persists the raw `whsec_` value.
</Warning>

## Filtering events

`event_types` accepts three forms, which you can mix in one array:

| Form              | Example                      | Matches                        |
| ----------------- | ---------------------------- | ------------------------------ |
| Exact type        | `"payment_intent.succeeded"` | Just that event.               |
| Resource wildcard | `"checkout.session.*"`       | Every event for that resource. |
| All events        | `"*"`                        | Every event type.              |

Subscribe only to what you handle — it keeps your endpoint's traffic and your logs focused. You can change the set any time with an update.

## List, retrieve, update

<CodeGroup>
  ```ts TypeScript theme={null}
  // List (most recent first). The secret is never included here.
  const endpoints = await ionic.webhookEndpoints.list();

  // Retrieve one
  const endpoint = await ionic.webhookEndpoints.retrieve({
    id: "whe_0J8kP2mNq7rVxYzAbCdEfGhI",
  });
  ```

  ```bash curl theme={null}
  # List (most recent first). The secret is never included here.
  curl https://api.ionicfi.com/v1/webhook_endpoints \
    -H "Authorization: Bearer sk_v1_test_..."

  # Retrieve one
  curl https://api.ionicfi.com/v1/webhook_endpoints/whe_0J8kP2mNq7rVxYzAbCdEfGhI \
    -H "Authorization: Bearer sk_v1_test_..."
  ```
</CodeGroup>

List responses wrap the array in `data`:

```json theme={null}
{ "data": [ { "id": "whe_0J8k...", "object": "webhook_endpoint", "...": "..." } ] }
```

Update with `POST` or `PATCH` and only the fields you're changing — `url`, `description`, or `event_types`:

<CodeGroup>
  ```ts TypeScript theme={null}
  const updated = await ionic.webhookEndpoints.patch({
    id: "whe_0J8kP2mNq7rVxYzAbCdEfGhI",
    "Idempotency-Key": "subscribe-all-events",
    body: { event_types: ["*"] },
  });
  ```

  ```bash curl theme={null}
  curl -X PATCH https://api.ionicfi.com/v1/webhook_endpoints/whe_0J8kP2mNq7rVxYzAbCdEfGhI \
    -H "Authorization: Bearer sk_v1_test_..." \
    -H "Idempotency-Key: subscribe-all-events" \
    -H "Content-Type: application/json" \
    -d '{ "event_types": ["*"] }'
  ```
</CodeGroup>

## Disable and delete

Disable an endpoint to stop deliveries while keeping it (and its secret) intact — useful during maintenance:

<CodeGroup>
  ```ts TypeScript theme={null}
  await ionic.webhookEndpoints.patch({
    id: "whe_0J8kP2mNq7rVxYzAbCdEfGhI",
    "Idempotency-Key": "pause-fulfillment-endpoint",
    body: { disabled: true },
  });
  ```

  ```bash curl theme={null}
  curl -X PATCH https://api.ionicfi.com/v1/webhook_endpoints/whe_0J8kP2mNq7rVxYzAbCdEfGhI \
    -H "Authorization: Bearer sk_v1_test_..." \
    -H "Idempotency-Key: pause-fulfillment-endpoint" \
    -H "Content-Type: application/json" \
    -d '{ "disabled": true }'
  ```
</CodeGroup>

`disabled` and `status` are two views of the same flag — `{"disabled": true}` is equivalent to `{"status": "disabled"}`, and `status` is only ever `active` or `disabled`. Re-enable with `{"disabled": false}`.

Delete removes the endpoint permanently:

<CodeGroup>
  ```ts TypeScript theme={null}
  await ionic.webhookEndpoints.delete({
    id: "whe_0J8kP2mNq7rVxYzAbCdEfGhI",
    "Idempotency-Key": "remove-fulfillment-endpoint",
  });
  ```

  ```bash curl theme={null}
  curl -X DELETE https://api.ionicfi.com/v1/webhook_endpoints/whe_0J8kP2mNq7rVxYzAbCdEfGhI \
    -H "Authorization: Bearer sk_v1_test_..." \
    -H "Idempotency-Key: remove-fulfillment-endpoint"
  ```
</CodeGroup>

## Rotate the signing secret

Rotating issues a new secret and returns it once. The previous secret keeps verifying for a brief overlap window, so deliveries already in flight (or signed just before the rotation) still pass — which is why a delivery's `webhook-signature` header [can carry more than one signature](/webhooks/signatures).

<CodeGroup>
  ```ts TypeScript theme={null}
  const rotated = await ionic.webhookEndpoints.rotateSecret({
    id: "whe_0J8kP2mNq7rVxYzAbCdEfGhI",
    "Idempotency-Key": "rotate-fulfillment-secret",
  });

  console.log(rotated.secret); // the new whsec_, returned once
  ```

  ```bash curl theme={null}
  curl -X POST https://api.ionicfi.com/v1/webhook_endpoints/whe_0J8kP2mNq7rVxYzAbCdEfGhI/rotate_secret \
    -H "Authorization: Bearer sk_v1_test_..." \
    -H "Idempotency-Key: rotate-fulfillment-secret"
  ```
</CodeGroup>

Roll out a rotation safely:

<Steps>
  <Step title="Rotate">Call `rotate_secret` and capture the new `whsec_` from the response.</Step>
  <Step title="Accept both">Deploy your handler to verify against the new secret while still accepting the old one during the overlap.</Step>
  <Step title="Retire the old">Once the window has passed and all deliveries verify with the new secret, drop the old one.</Step>
</Steps>
