Lambda Powertools Idempotency: DynamoDB Keys That Survive Retries

Lambda Powertools Idempotency: DynamoDB Keys That Survive Retries
19 views
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_PROGRESS locking so concurrent duplicates wait instead of double-charging. Pair with Bedrock Agents: Idempotent Tool Calls and Lambda Warm Pools.

Lambda Powertools Idempotency: production guidance

typescript
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 });
typescript
// 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

typescript
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

typescript
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

typescript
// 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
  • [ ] throwOnNoIdempotencyKey for 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

Last updated on September 11, 2026 Related: Lambda Powertools for Node: Structured Logs That Survive On-Call; How to Build a Hyper-Scale Serverless API Platform on AWS with API Gateway, Lambda, DynamoDB, and CloudFront; Production IoT Telemetry on AWS: Kinesis, Lambda, DynamoDB, S3, and Event-Driven Device Command Workflows.

Watch: 100 Days of AI on YouTube

Short videos from CheatCoders, one AI engineering topic per day.

Open the playlist on YouTube · CheatCoders on YouTube

Deep-dive PDF

Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.

Already subscribed? or open the subscribe page.


Discover more from CheatCoders

Subscribe to get the latest posts sent to your email.

Comments

No comments yet. Why don’t you start the discussion?

Leave a comment

No account needed. Name and email are optional.