# DELETE /partner/webhooks/{webhook_id}

Permanently delete a notification subscription and the signing secret attached to it. Requires the webhooks:write scope, and mandatory confirmation with the URL of the subscription.

Source: https://docs.sealtrust.io/en/reference/delete-partner-webhooks-id/

---

You permanently delete a notification subscription of your brand. When you leave
this page, you will know how to build the call, including the mandatory
confirmation, and you will know what the deletion destroys along with the
subscription.

Full address:

```http
DELETE https://api.sealtrust.io/v1/partner/webhooks/{webhook_id}?confirm={url}
```

The same endpoint also answers without the `/v1` prefix, at
`https://api.sealtrust.io/partner/webhooks/{webhook_id}`. Both addresses call
the same code. Use the `/v1` form for a new integration.

> [!DANGER] This deletion is permanent
> The subscription stops receiving events and the secret you supplied at
> creation, or during a modification, is erased from our database. If you then
> recreate a subscription for the same URL, you are the one who chooses its
> secret: reuse the same one and your receiving system keeps checking the
> signatures, choose another one and you have to reconfigure it. A subscription
> created without a secret receives unsigned deliveries, and there is then no
> secret to destroy. There is no undo.

## Authorization

API key in the `Authorization` header, in the `Bearer` format.

The deletion itself requires the `webhooks:write` scope. Reading the URL
beforehand, needed to build the confirmation, additionally requires the
`webhooks:read` scope. In practice, the key that runs this sequence carries both
scopes. A key that does not carry the required scope receives a 403 whose
message names the missing scope.

A subscription is only visible and deletable by a key of the brand it belongs
to. A subscription number that belongs to another brand answers 404, like a
number that does not exist. The response never says whether the subscription
exists elsewhere.

This deletion stays open whatever your plan. If your plan no longer includes
notifications, you can still delete the subscriptions recorded in your name. It
is creation and modification that require the plan to include notifications.

### Mandatory confirmation

On top of the scopes, the call requires a confirmation. The server compares the
value of the `confirm` query parameter with the URL it has stored for that
subscription number. Three cases.

| What you send | Response | Effect |
| --- | --- | --- |
| `confirm` absent or empty | 400, code `CONFIRMATION_REQUIRED` | Nothing is deleted. |
| `confirm` different from the stored URL | 400, code `CONFIRMATION_MISMATCH` | Nothing is deleted. |
| `confirm` equal to the stored URL | 204 | The subscription and its secret are destroyed. |

The comparison tolerates case, leading and trailing spaces, and equivalent
Unicode forms. It tolerates nothing else. One punctuation character too many or
one missing URL segment gives a `CONFIRMATION_MISMATCH`.

The error message never returns the expected URL to you. Read it with
`GET /v1/partner/webhooks/{webhook_id}` or in the list returned by
`GET /v1/partner/webhooks`, `url` field. Both of these reads require the
`webhooks:read` scope.

> [!ATTENTION] A call that sends only the subscription number is refused
> This is a deliberate breaking change. A call that used to work by sending only
> the number today receives a 400 `CONFIRMATION_REQUIRED`. Add the confirmation
> to your code.

## Rate limit

The rate is measured over a fixed window of 60 seconds. We first apply the value
set on your brand, if we have set one. Otherwise the one of your plan. In the
absence of both, the fallback value is 120 calls per window.

Two counters are layered, with the same limit: one per key, one for the sum of
all the keys of your brand. Creating extra keys therefore does not increase the
total rate allowed.

The response carries two sets of `X-RateLimit-*` headers, and both sets carry
the same header names. Keep the values of the set that comes with
`X-RateLimit-Scope`: that is the one describing the limit of your key or the one
of your brand. The second set comes from a per-IP-address counter and does not
describe the budget of your key. Read the complete list of the headers of the
response before trusting a value.

| Header | Content |
| --- | --- |
| `X-RateLimit-Limit` | the limit applied over the window |
| `X-RateLimit-Remaining` | what is left to you in the current window |
| `X-RateLimit-Reset` | the timestamp of the end of the window, in seconds |
| `X-RateLimit-Scope` | `key` or `brand`, the counter that served as reference |

The set that carries `X-RateLimit-Scope` is present on the 204 response and on
the error responses that come after the rate check.

> [!ATTENTION] These headers are read from a server
> Code running in a browser sees neither `X-RateLimit-*`, nor `Retry-After`, nor
> `x-request-id`. Our cross-origin sharing policy only exposes `x-total-count`.
> A partner API key has no place in a browser anyway, where anyone could read
> it.

A refusal returns 429 with `Retry-After`, expressed in seconds remaining in the
current window. That value is never lower than 1.

> [!INFO] This deletion consumes no quota
> The daily quota of your key is not eaten into by this call, and neither is the
> monthly product quota of your plan. Only the rate limit applies.

> [!ATTENTION] An outage of the rate limiting service refuses the call
> When the service that counts the calls is momentarily unavailable, the
> response is 503 and nothing is deleted. Try again later, then check the state
> of the subscription with `GET /v1/partner/webhooks/{webhook_id}`.

## Path and query parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `webhook_id` | `integer` | yes | The number of the subscription to delete, as creation, listing and reading return it in the `id` field. |
| `confirm` | `string` | yes | The registered URL of the subscription, exactly as `GET /v1/partner/webhooks/{webhook_id}` returns it in the `url` field. Absent or different, the deletion is refused with 400. |

### Headers

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `Authorization` | `string` | yes | `Bearer` followed by your API key. |

## Request body

This endpoint has no request body. All the information you send fits in the
subscription number and in the `confirm` parameter.

## Example request

Deletion of subscription number 7, whose registered URL is
`https://exemple-sas.test/sealtrust/evenements`. The three examples do the same
thing: they read the subscription, then they delete it by sending its URL back.
The key used carries both the `webhooks:read` and `webhooks:write` scopes, the
read of the first step requiring the former and the deletion of the second
requiring the latter.

:::onglets
```bash title="curl"
# 1. Read the subscription to learn its registered URL.
curl -i https://api.sealtrust.io/v1/partner/webhooks/7 \
  -H "Authorization: Bearer st_test_0000000000000000000000000000000000000000000000"

# 2. Delete by sending that URL back in confirm.
curl -i -X DELETE \
  "https://api.sealtrust.io/v1/partner/webhooks/7?confirm=https://exemple-sas.test/sealtrust/evenements" \
  -H "Authorization: Bearer st_test_0000000000000000000000000000000000000000000000"
```
```typescript
import { SealTrustClient } from "@sealtrust-io/sdk";

const sealtrust = new SealTrustClient({
  apiKey: "st_test_0000000000000000000000000000000000000000000000",
});

// 1. Read the subscription to learn its registered URL.
const abonnement = await sealtrust.webhooks.get(7);
console.log(abonnement.url); // https://exemple-sas.test/sealtrust/evenements

// 2. Delete by sending that URL back.
await sealtrust.webhooks.delete(abonnement.id, abonnement.url);
```
```python
import requests

entetes = {
    "Authorization": "Bearer st_test_0000000000000000000000000000000000000000000000",
}

# 1. Read the subscription to learn its registered URL.
lecture = requests.get(
    "https://api.sealtrust.io/v1/partner/webhooks/7",
    headers=entetes,
    timeout=30,
)
lecture.raise_for_status()
adresse = lecture.json()["url"]
print(adresse)  # https://exemple-sas.test/sealtrust/evenements

# 2. Delete by sending that URL back.
suppression = requests.delete(
    "https://api.sealtrust.io/v1/partner/webhooks/7",
    headers=entetes,
    params={"confirm": adresse},
    timeout=30,
)

print(suppression.status_code)  # 204
```
:::

The `confirm` parameter travels in the URL of the request. If your tool does not
do it for you, percent-encode its value before writing it.

> [!INFO] What the confirmation catches, and what it does not
> It stops the deletion from acting on a number alone. So it catches a stale
> number, a number guessed by walking a run of integers, or a number copied from
> another environment: in those three cases, the URL you send does not match the
> stored one, and you receive a 400 instead of a deletion. It catches nothing
> else. A script that always rereads the URL from the same number will confirm
> the wrong subscription just as well as the right one. When you already hold
> the URL in your configuration, send it from your configuration.

## Example response

HTTP status code `204`. The body is empty.

```http
HTTP/1.1 204 No Content
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 118
X-RateLimit-Reset: 1755676800
X-RateLimit-Scope: key
x-ratelimit-limit: 600
x-ratelimit-remaining: 597
x-ratelimit-reset: 1755676800
x-request-id: 00000000000000000000000000000000
```

The first four headers describe the limit of your key or of your brand. The next
three come from the per-IP-address counter. `x-request-id` identifies the call
in our logs and comes with every response.

After that 204, the subscription no longer appears in
`GET /v1/partner/webhooks`, and `GET /v1/partner/webhooks/{webhook_id}` answers
404 for that number. Events are no longer delivered to that URL.

## Errors

The body of an error response carries a `detail` field. On the two confirmation
refusals, that field is an object containing `code`, `message` and
`what_to_type`. In the other cases, it contains a sentence or a list.

Example body of a confirmation refusal, HTTP status code `400`:

```json
{
  "detail": {
    "code": "CONFIRMATION_MISMATCH",
    "message": "What you typed is not the subscription's url. Nothing was changed. This endpoint stops receiving events and its signing secret is destroyed. A new subscription for the same url gets a different secret, so the receiver must be reconfigured. Read the url from GET /partner/webhooks/{id}.",
    "what_to_type": "the subscription's url"
  }
}
```

That message is returned as is by the service, in English. On the secret, the
sentence to remember is the one in the callout at the top of the page: the
secret of a new subscription is the one you choose.

| Code | Condition | What to do |
| --- | --- | --- |
| 400 | `confirm` is absent or empty. `detail.code` is `CONFIRMATION_REQUIRED`. Nothing was deleted. | Read the subscription with `GET /v1/partner/webhooks/{webhook_id}` and send its `url` field back in `confirm`. |
| 400 | `confirm` does not match the registered URL of that subscription. `detail.code` is `CONFIRMATION_MISMATCH`. Nothing was deleted. | The subscription number does not designate the subscription you think it does. Read it again before starting over. The message never returns the expected URL to you. |
| 401 | The `Authorization` header is absent. `detail` is `Missing Authorization header`. The response also carries `WWW-Authenticate: Bearer`. | Add the header. |
| 401 | The `Authorization` header does not start with `Bearer ` followed by a space. `detail` is `Invalid Authorization header format (expected 'Bearer <token>')`. The response also carries `WWW-Authenticate: Bearer`. | Correct the shape of the header. |
| 401 | The value sent after `Bearer ` is shorter than 40 characters. `detail` is `Invalid API key format`. | Check that you are sending the complete secret, without truncation, without a space or a line break. This response does not carry `WWW-Authenticate`. |
| 401 | The key is unknown to our records. `detail` is `Invalid API key`. | Check that you are using a key of this environment, and that it has not been replaced. This response does not carry `WWW-Authenticate`. |
| 403 | The key is not in the `active` status. `detail` is `API key is <status>` and names the real status, for example `revoked`. | Create a new key in the console. |
| 403 | The key has reached its expiry date. `detail` is `API key has expired`. | Create a new key. The old one will never become valid again. |
| 403 | The key does not carry the `webhooks:write` scope. `detail` is `Missing required scope: webhooks:write`. | Create a key carrying that scope. The scopes of an existing key cannot be changed. |
| 404 | No subscription carries that number in your brand. `detail` is `Webhook subscription not found`. | Check the number with `GET /v1/partner/webhooks`. A subscription of another brand gives the same response. So does a subscription already deleted. |
| 422 | The number sent in the path is not an integer. `detail` is a list that names the parameter at fault. | Send the `id` field as the read returns it, without quotation marks and without a decimal. |
| 422 | The number sent is an integer outside the bounds the service accepts. `detail` is a generic sentence that does not name the parameter. | Send a subscription number returned by the list. |
| 429 | The rate limit of the key is reached. `Retry-After` and the `X-RateLimit-*` family come with the response, with `X-RateLimit-Scope: key`. Nothing was deleted. | Wait the number of seconds indicated by `Retry-After`, then try again. |
| 429 | The rate limit of the brand is reached, all keys taken together. `X-RateLimit-Scope` is `brand`. Nothing was deleted. | Wait the number of seconds indicated by `Retry-After`. Creating an extra key does not raise that limit. |
| 500 | The subscription has no registered URL, so there is nothing to confirm. `detail.code` is `CONFIRMATION_IMPOSSIBLE`. Nothing was deleted. | Contact support quoting the subscription number and the value of `x-request-id`. |
| 500 | An unexpected error occurred while your call was being processed. `detail` is a short sentence, with no technical detail, and the response carries an `x-request-id` header. | Check the state of the subscription with `GET /v1/partner/webhooks/{webhook_id}` before starting over. If the error persists, contact support quoting the value of `x-request-id`. |
| 503 | The call rate limiting service is momentarily unavailable. `detail` is `Rate limiting temporarily unavailable, please retry shortly`. The call is refused and nothing is deleted. | Try again in a few moments. |

A deletion already carried out returns 404 on the second call. This endpoint is
therefore not replayable: treat the 404 as the confirmation that the
subscription no longer exists.

## See also

- [`GET /partner/webhooks/{webhook_id}`](/en/reference/get-partner-webhooks-id/),
  read a subscription and its delivery state.
- [`PUT /partner/webhooks/{webhook_id}`](/en/reference/put-partner-webhooks-id/),
  change the URL, the events or the secret of a subscription.
- [`GET /partner/webhooks`](/en/reference/get-partner-webhooks/),
  list your notification subscriptions, page by page.
- [Receive events by webhook](/en/webhooks/),
  create a subscription, check a signature, catch up on lost events.
