feat: add idempotency guard to onWebhook
Prevent duplicate comments on rapid Paperclip retries: - Check plugin instance state for requestId before processing - Skip if same requestId was seen within 5 minutes - Store requestId with ISO timestamp after first successful delivery Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -831,8 +831,27 @@ const plugin = definePlugin({
|
||||
async onWebhook(input: PluginWebhookInput): Promise<void> {
|
||||
if (!pluginCtx) return; // not yet initialized
|
||||
|
||||
// TODO: add idempotency guard using input.requestId to prevent duplicate
|
||||
// comments on rapid retries (store requestId in plugin state with ~60s TTL)
|
||||
// Idempotency guard: skip duplicate deliveries within 5 minutes
|
||||
if (input.requestId) {
|
||||
const idempKey = `webhook-idem-${input.requestId}`;
|
||||
const seenAt = await pluginCtx.state.get({
|
||||
scopeKind: "instance",
|
||||
stateKey: idempKey,
|
||||
});
|
||||
if (seenAt && typeof seenAt === "string") {
|
||||
const ageMs = Date.now() - new Date(seenAt).getTime();
|
||||
if (ageMs < 5 * 60 * 1000) {
|
||||
pluginCtx.logger.info(
|
||||
`onWebhook: skipping duplicate requestId ${input.requestId} (age ${Math.round(ageMs / 1000)}s)`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
await pluginCtx.state.set(
|
||||
{ scopeKind: "instance", stateKey: idempKey },
|
||||
new Date().toISOString(),
|
||||
);
|
||||
}
|
||||
|
||||
const { endpointKey, parsedBody } = input;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user