## Context
While going through the webhook router I noticed the DELETE route (`…DELETE /api/v1/webhooks/:webhookId`) is the only one without a `response` schema. Because of that, Fastify doesn't filter anything and just sends back whatever `deleteWebhook` returns, which is the raw `webhooks` row.
So deleting a webhook currently returns fields that never show up on any other webhook route:
- `encryptedUrl` and `encryptedPassKey` (serialized as Buffers)
- the old pre-KMS columns: `urlCipherText`, `urlIV`, `urlTag`, `encryptedSecretKey`, `iv`, `tag`, `algorithm`, `keyEncoding`
- the raw `url` column and the internal `filteredEvents` column
Everything there is still encrypted (and the legacy `url` column was blanked by the KMS migration), so nothing readable leaks. But it's internal data that shouldn't be part of the response, and it doesn't match the other routes.
The fix adds the same `response` schema PATCH already uses (`sanitizedWebhookSchema`). After this, DELETE returns the same fields as the other webhook routes.
A few things I checked so this doesn't break anyone:
- The status code (200) and the `{ message, webhook }` shape are unchanged.
- The frontend (`useDeleteWebhook`) doesn't read the response body.
- The DELETE audit log only uses `environment.slug`, `id`, `isDisabled` and `secretPath`, and all of those are still in the schema.
- I didn't add `url` (the way GET does), because `deleteWebhook` never decrypts it. The only `url` available there is the empty legacy column, so `z.string()` would fail on `null`.
I also added `e2e-test/routes/v1/webhook.spec.ts`. It checks the exact list of keys in the DELETE response rather than just checking that `encryptedUrl` is missing, so any column added later can't slip through this route unnoticed.
## Screenshots
N/A, backend-only change.
## Steps to verify the change
1. Run the new spec: `make test-api-e2e SPEC=webhook.spec`. It passes.
2. Revert the router change and run it again. It fails and lists every leaked key (`encryptedUrl`, `encryptedPassKey`, `urlCipherText`, ...). I ran both locally.
3. Manual check: create a webhook in project settings, delete it, and look at the DELETE response in the network tab.
Before (trimmed):
```json
"webhook": {
"id": "...",
"secretPath": "/",
"encryptedUrl": { "type": "Buffer", "data": [244, 40, 124, "..."] },
"encryptedPassKey": { "type": "Buffer", "data": [149, 210, "..."] },
"urlCipherText": null, "urlIV": null, "urlTag": null,
"encryptedSecretKey": null, "iv": null, "tag": null,
"algorithm": null, "keyEncoding": null, "url": null,
"filteredEvents": ["secrets.modified", "..."],
"...": "..."
}
```
After:
```json
"webhook": {
"id": "...",
"secretPath": "/",
"lastStatus": null,
"lastRunErrorMessage": null,
"isDisabled": false,
"createdAt": "...",
"updatedAt": "...",
"envId": "...",
"type": "general",
"projectId": "...",
"environment": { "id": "...", "name": "Development", "slug": "dev" },
"eventsFilter": [{ "eventName": "secrets.modified" }, "..."]
}
```
I couldn't run `lint:fix` / `type:check` locally (my machine runs out of memory on them), so I'm relying on CI for those. The change is the same 6-line block PATCH uses, so I don't expect any issues.
## Type
- [x] Fix
- [ ] Feature
- [ ] Improvement
- [ ] Breaking
- [ ] Docs
- [ ] Chore
## Checklist
- [x] Title follows the [conventional commit](https://www.conventionalcommits.org/en/v1.0.0/#summary) format: `type(scope): short description` (scope is optional, e.g., `fix: prevent crash on sync` or `fix(api): handle null response`).
- [x] Tested locally
- [ ] Updated docs (if needed)
- [ ] Updated CLAUDE.md files (if needed)
- [x] Read the [contributing guide](https://infisical.com/docs/contributing/getting-started/overview)