{"record":{"id":"2cbf6d1e1d9a8446","repo":"fullstackhero/dotnet-starter-kit","slug":"webhook-subscription-command-id-not-found","errorCode":null,"errorMessage":"Webhook subscription {command.Id} not found.","messagePattern":"Webhook subscription (.+?) not found\\.","errorType":"exception","errorClass":"NotFoundException","httpStatus":404,"severity":"error","filePath":"src/Modules/Webhooks/Modules.Webhooks/Features/v1/DeleteWebhookSubscription/DeleteWebhookSubscriptionCommandHandler.cs","lineNumber":19,"sourceCode":"using FSH.Framework.Core.Exceptions;\nusing FSH.Modules.Webhooks.Contracts.v1.DeleteWebhookSubscription;\nusing FSH.Modules.Webhooks.Data;\nusing Mediator;\nusing Microsoft.EntityFrameworkCore;\n\nnamespace FSH.Modules.Webhooks.Features.v1.DeleteWebhookSubscription;\n\npublic sealed class DeleteWebhookSubscriptionCommandHandler(\n    WebhookDbContext dbContext) : ICommandHandler<DeleteWebhookSubscriptionCommand>\n{\n    public async ValueTask<Unit> Handle(DeleteWebhookSubscriptionCommand command, CancellationToken cancellationToken)\n    {\n        ArgumentNullException.ThrowIfNull(command);\n\n        var subscription = await dbContext.Subscriptions\n            .FirstOrDefaultAsync(s => s.Id == command.Id, cancellationToken)\n            .ConfigureAwait(false)\n            ?? throw new NotFoundException($\"Webhook subscription {command.Id} not found.\");\n\n        dbContext.Subscriptions.Remove(subscription);\n        await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);\n\n        return Unit.Value;\n    }\n}\n","sourceCodeStart":1,"sourceCodeEnd":27,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Webhooks/Modules.Webhooks/Features/v1/DeleteWebhookSubscription/DeleteWebhookSubscriptionCommandHandler.cs#L1-L27","documentation":"DeleteWebhookSubscription's handler loads the subscription by command Id and throws NotFoundException when none matches, before calling Remove. The webhook module therefore rejects deletion of unknown, soft-deleted/filtered, or out-of-tenant subscriptions with a typed not-found (HTTP 404) instead of silently doing nothing.","triggerScenarios":"Calling DeleteWebhookSubscription with a subscription Id that was already deleted (second delete of the same id), an id from another tenant, or a fabricated/mistyped GUID.","commonSituations":"Double-clicking a delete button so the second request 404s; a client list that wasn't refreshed after another admin removed the subscription; copying an id from logs of a different environment (staging vs production).","solutions":["Treat the 404 as success for idempotent delete UX (the desired end state — subscription gone — is already achieved).","Refresh the subscription list before deleting to confirm it still exists.","Check the tenant context matches where the subscription was created.","Verify the GUID is copied from the correct environment's list endpoint."],"exampleFix":"// before (client)\nawait apiFetch(`/webhooks/v1/subscriptions/${id}`, { method: 'DELETE' }); // 404 on double delete\n// after\ntry {\n  await apiFetch(`/webhooks/v1/subscriptions/${id}`, { method: 'DELETE' });\n} catch (e) {\n  if (e.status !== 404) throw e; // idempotent: 404 already deleted\n}","handlingStrategy":"try-catch","validationCode":"const sub = await api.getSubscription(id);\nif (!sub) return; // already gone — delete goal achieved","typeGuard":null,"tryCatchPattern":"try {\n  await deleteSubscription(id);\n} catch (ApiError e) when (e.Status === 404) {\n  // idempotent success\n  removeFromLocalList(id);\n}","preventionTips":["Treat delete as idempotent on the client: 404 means already deleted.","Disable delete buttons after first click to avoid duplicate requests.","Refresh lists after mutations so stale ids are not reused."],"tags":["ef-core","not-found","webhooks","cqrs","idempotency"],"backgroundTag":"entity-not-found","analyzedSha":"3f2959e683e9f83f13e55e1678c9119f63c7e8e5","analyzedAt":"2026-09-15T22:20:53.684Z","contentChangedAt":"2026-09-15T22:20:53.684Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}