{"record":{"id":"046ddf27252e8020","repo":"pinpoint-apm/pinpoint","slug":"there-should-be-webhooksendinfoid-to-delete-webhoo","errorCode":null,"errorMessage":"there should be webhookSendInfoId to delete webhook","messagePattern":"there should be webhookSendInfoId to delete webhook","errorType":"validation","errorClass":"ResponseStatusException","httpStatus":400,"severity":"error","filePath":"webhook/src/main/java/com/navercorp/pinpoint/web/webhook/controller/WebhookSendInfoController.java","lineNumber":53,"sourceCode":"\n    public WebhookSendInfoController(WebhookSendInfoService webhookSendInfoService) {\n        this.webhookSendInfoService = Objects.requireNonNull(webhookSendInfoService, \"webhookSendInfoService\");\n    }\n\n\n    @PostMapping()\n    public WebhookSendInfoResponse insertWebhookSendInfo(@RequestBody WebhookSendInfo webhookSendInfo) {\n        if (!StringUtils.hasText(webhookSendInfo.getRuleId()) || !StringUtils.hasText(webhookSendInfo.getWebhookId())) {\n            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, \"there should be ruleId and webhookId to insert webhookSendInfo\");\n        }\n        String webhookSendInfoId = webhookSendInfoService.insertWebhookSendInfo(webhookSendInfo);\n        return new WebhookSendInfoResponse(Result.SUCCESS, webhookSendInfoId);\n    }\n\n    @DeleteMapping()\n    public Response deleteWebhookSendInfo(@RequestBody WebhookSendInfo webhookSendInfo) {\n        if (!StringUtils.hasText(webhookSendInfo.getWebhookSendInfoId())) {\n            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, \"there should be webhookSendInfoId to delete webhook\");\n        }\n        webhookSendInfoService.deleteWebhookSendInfo(webhookSendInfo);\n        return SimpleResponse.ok();\n    }\n\n    @GetMapping()\n    public List<WebhookSendInfo> getWebhookSendInfo(@RequestParam(value=WEBHOOK_ID, required=false) String webhookId,\n                                                    @RequestParam(value=RULE_ID, required=false) String ruleId) {\n        if (!StringUtils.hasText(webhookId) && !StringUtils.hasText(ruleId)) {\n            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, \"Either webhookId or ruleId is needed to get webhook send information\");\n        }\n\n        if (StringUtils.hasText(webhookId)) {\n            return webhookSendInfoService.selectWebhookSendInfoByWebhookId(webhookId);\n        }\n\n        return webhookSendInfoService.selectWebhookSendInfoByRuleId(ruleId);\n    }","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/pinpoint-apm/pinpoint/blob/744c3d3075e595656abb1ae331ad2c0e4c9eb996/webhook/src/main/java/com/navercorp/pinpoint/web/webhook/controller/WebhookSendInfoController.java#L35-L71","documentation":"The webhook send-info DELETE endpoint in Pinpoint web requires the request body to carry a non-empty webhookSendInfoId. When the body's webhookSendInfoId is missing, null, or blank, the controller rejects the request before calling the service by throwing a 400 BAD_REQUEST ResponseStatusException. This guards against deleting webhook send-info rows without a valid identifier.","triggerScenarios":"Sending a DELETE request to the webhook send-info endpoint with an empty JSON body, a body lacking the webhookSendInfoId field, or a body where webhookSendInfoId is \"\" or whitespace.","commonSituations":"Frontend code constructs the WebhookSendInfo object from a row that was never persisted (no generated ID yet); API consumers copy a payload shape from the create endpoint and forget the ID; JSON field-name mismatch (e.g. webhookSendInfoID) silently yields null.","solutions":["Include a non-empty webhookSendInfoId in the DELETE request body (fetch it first via the GET endpoint if needed).","Verify the JSON field name is exactly webhookSendInfoId so Jackson populates it.","Client-side: check StringUtils.hasText / string truthiness of the id before issuing the DELETE call.","If deleting a webhook, delete its send-info entries using IDs returned when the webhook was created or listed."],"exampleFix":"// before\ndeleteWebhookSendInfo({ webhookId: 'w-1' });\n// after\ndeleteWebhookSendInfo({ webhookSendInfoId: sendInfo.webhookSendInfoId, webhookId: sendInfo.webhookId, ruleId: sendInfo.ruleId });","handlingStrategy":"validation","validationCode":"if (!sendInfo || typeof sendInfo.webhookSendInfoId !== 'string' || sendInfo.webhookSendInfoId.trim() === '') {\n  throw new Error('webhookSendInfoId is required to delete webhook send info');\n}","typeGuard":"const hasSendInfoId = (v: unknown): v is { webhookSendInfoId: string } =>\n  typeof v === 'object' && v !== null && typeof (v as any).webhookSendInfoId === 'string' && (v as any).webhookSendInfoId.trim().length > 0;","tryCatchPattern":"try {\n  await deleteWebhookSendInfo(sendInfo);\n} catch (e) {\n  if (e?.status === 400 && /webhookSendInfoId/.test(e.message)) {\n    showError('Cannot delete: webhook send info has no ID. Reload and retry.');\n  } else throw e;\n}","preventionTips":["Only delete rows that were fetched from the server (they always have a generated webhookSendInfoId).","Validate the ID client-side before issuing DELETE requests.","Use consistent field names between frontend models and the WebhookSendInfo Java DTO.","Handle 400 responses in the API client with a readable message instead of failing silently."],"tags":["rest-api","bad-request","validation","webhook"],"backgroundTag":"missing-required-argument","analyzedSha":"744c3d3075e595656abb1ae331ad2c0e4c9eb996","analyzedAt":"2026-09-07T18:48:45.289Z","contentChangedAt":"2026-09-07T18:48:45.289Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}