{"record":{"id":"fb1a0200cee84ecb","repo":"pinpoint-apm/pinpoint","slug":"there-should-be-ruleid-and-webhookid-to-insert-web","errorCode":null,"errorMessage":"there should be ruleId and webhookId to insert webhookSendInfo","messagePattern":"there should be ruleId and webhookId to insert webhookSendInfo","errorType":"validation","errorClass":"ResponseStatusException","httpStatus":400,"severity":"warning","filePath":"webhook/src/main/java/com/navercorp/pinpoint/web/webhook/controller/WebhookSendInfoController.java","lineNumber":44,"sourceCode":"@RestController\n@RequestMapping(value={\"/api/webhookSendInfo\", \"/api/application/webhookSendInfo\"})\npublic class WebhookSendInfoController {\n    private final Logger logger = LogManager.getLogger(this.getClass());\n\n    public final static String WEBHOOK_ID = \"webhookId\";\n    public final static String RULE_ID = \"ruleId\";\n\n    private final WebhookSendInfoService webhookSendInfoService;\n\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)) {","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/pinpoint-apm/pinpoint/blob/744c3d3075e595656abb1ae331ad2c0e4c9eb996/webhook/src/main/java/com/navercorp/pinpoint/web/webhook/controller/WebhookSendInfoController.java#L26-L62","documentation":"WebhookSendInfoController.insertWebhookSendInfo links an alarm rule to a webhook, so both ruleId and webhookId must be present in the POST body. When either is missing or empty, it throws HTTP 400 before calling webhookSendInfoService.insertWebhookSendInfo, since a link row without both keys cannot be created.","triggerScenarios":"POST /webhookSendInfo with body missing ruleId, missing webhookId, or containing empty strings for either; wrong key casing (rule_id/webhook_id) leaving the DTO fields null.","commonSituations":"UI automation that creates the rule and webhook asynchronously and posts the link before ids are available; payloads copied from the Webhook controller (which uses webhookId only) missing ruleId; nulls after a failed rule creation.","solutions":["Include both non-empty fields in the POST body: {\"ruleId\":\"<alarmRuleId>\",\"webhookId\":\"<webhookId>\",\"webhookSendInfoId\":null}","Create/get the alarm rule id first via the alarm rule API if unknown","Create/get the webhook id first via GET /webhook if unknown","Check JSON key casing matches the WebhookSendInfo DTO (ruleId, webhookId)"],"exampleFix":"// before\n{\"ruleId\":\"r-123\"}\n// after\n{\"ruleId\":\"r-123\",\"webhookId\":\"e2f1a3b4c5d6\"}","handlingStrategy":"validation","validationCode":"function canInsertWebhookSendInfo(x) { return Boolean(x && x.ruleId?.trim() && x.webhookId?.trim()); }","typeGuard":"function hasRuleAndWebhookIds(x) { return typeof x.ruleId === 'string' && x.ruleId.length > 0 && typeof x.webhookId === 'string' && x.webhookId.length > 0; }","tryCatchPattern":"try { await api.insertWebhookSendInfo(body); } catch (e) { if (e.response?.status === 400 && e.response?.data?.message?.includes('ruleId and webhookId')) { /* resolve missing id, then retry */ } throw e; }","preventionTips":["Await creation of both the alarm rule and webhook before posting the link record","Carry ruleId and webhookId together in one context object to avoid dropping one","Add a pre-send assertion that both ids are non-empty strings","Keep key names ruleId/webhookId exactly as the server DTO expects"],"tags":["http-400","rest-api","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"}