diff --git a/src/worker.ts b/src/worker.ts index 5ac952b..f2ddbf1 100644 --- a/src/worker.ts +++ b/src/worker.ts @@ -831,8 +831,27 @@ const plugin = definePlugin({ async onWebhook(input: PluginWebhookInput): Promise { 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;