Retries are not edge cases — they are the product. API Gateway clients time out and retry, EventBridge at-least-once delivers twice, Step Functions redrive, and your deploy swaps code mid-flight. Powertools idempotency is only an unfair advantage when the DynamoDB key is stable across those realities and the persistence window outlives the retry storm.
⚡ TL;DR: Hash a business idempotency key (not the whole event). Scope by function alias/version when payloads are version-sensitive. Set expiry past your max client retry + DLQ redrive window. Use
IN_PROGRESSlocking so concurrent duplicates wait instead of double-charging. Pair with Bedrock Agents: Idempotent Tool Calls and Lambda Warm Pools.
Key from business identity, not raw event JSON
import { makeIdempotent, IdempotencyConfig } from "@aws-lambda-powertools/idempotency";
import { DynamoDBPersistenceLayer } from "@aws-lambda-powertools/idempotency/dynamodb";
const persistence = new DynamoDBPersistenceLayer({ tableName: process.env.IDEMP_TABLE! });
// GOOD: stable across redelivery jitter (new eventId, same orderId)
const config = new IdempotencyConfig({
eventKeyJmesPath: "detail.orderId",
expiresAfterSeconds: 60 * 60 * 24, // 24h > client retry + DLQ
useLocalCache: true,
throwOnNoIdempotencyKey: true,
});
async function charge(event: { detail: { orderId: string; amount: number } }) {
return { charged: event.detail.amount, orderId: event.detail.orderId };
}
export const handler = makeIdempotent(charge, { persistenceStore: persistence, config });
// BAD: whole-event hash — EventBridge envelope changes → duplicate charge
const badConfig = new IdempotencyConfig({
eventKeyJmesPath: "@", // unique every delivery
expiresAfterSeconds: 30, // shorter than client retry budget
});
void badConfig;
✅ JMESPath to orderId / Idempotency-Key header.
❌ Hashing timestamps, eventId, or request context that changes on retry.
API Gateway header keys that clients can control
import type { APIGatewayProxyEventV2 } from "aws-lambda";
const httpConfig = new IdempotencyConfig({
eventKeyJmesPath: 'headers."idempotency-key"',
expiresAfterSeconds: 60 * 15,
throwOnNoIdempotencyKey: true,
});
export const apiHandler = makeIdempotent(
async (event: APIGatewayProxyEventV2) => {
const body = JSON.parse(event.body ?? "{}");
return { statusCode: 200, body: JSON.stringify(await createPayment(body)) };
},
{ persistenceStore: persistence, config: httpConfig },
);
Reject missing keys with 400 at the edge. Document that retries must reuse the same Idempotency-Key.
Survive deploys and concurrent duplicates
const deploySafe = new IdempotencyConfig({
eventKeyJmesPath: "detail.orderId",
// Include payload hash when handler semantics change across versions
payloadValidationJmesPath: "detail",
expiresAfterSeconds: 86_400,
});
During blue/green, payloadValidationJmesPath prevents returning a cached response from an old code path when the body shape changed. Concurrent invokes: Powertools writes IN_PROGRESS; second caller either waits or gets ThereIsAlreadyAExecutionInProgress — handle that as 409/retry, never as “charge again.”
DynamoDB table shape that will not surprise you
// PK: id (idempotency key), attrs: expiration, status, data_hash, response_data
// Enable TTL on expiration attribute. Provision enough WCU for peak retry storms.
Watch ConditionalCheckFailed and latency on the idempotency table during incidents — a hot partition on a popular orderId prefix needs random suffixing only if you control both writer and key design (usually you should not shard the business key).
Closing checklist
- [ ] Idempotency key = business id or client
Idempotency-Key, not full event - [ ] TTL/expiry > max client retry + DLQ/redrive window
- [ ]
throwOnNoIdempotencyKeyfor mutating APIs - [ ] Payload validation enabled across versioned deploys when needed
- [ ] Table TTL on; alarms on ConditionalCheckFailed and throttle
- [ ] Concurrent in-progress treated as retry/409, never double side-effect
Related reading
- Bedrock Agents: Idempotent Tool Calls Against DynamoDB Writes
- Lambda Warm Pools: Low-Latency Backends for Coding Agent Tools
- Cross-Account Bedrock Access: Platform Teams Without Shared Keys
- OpenTelemetry for LLMs: Trace Prompt Latency Across Microservices
Last updated on September 11, 2026
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.
