Key Takeaways

  • The most common pattern pairs Stripe's hosted Checkout or Elements with an AWS backend (API Gateway + Lambda) so raw card data never touches your infrastructure, minimizing PCI DSS scope.
  • Stripe webhooks (payment succeeded, subscription updated, dispute created) are the source of truth for order fulfillment — don't rely solely on the client-side redirect.
  • Store the Stripe secret key in AWS Secrets Manager, never in environment variables committed to code or Lambda console plaintext config without encryption context.
  • Webhook signature verification is mandatory — without it, anyone can POST fake "payment succeeded" events to your endpoint.
  • Idempotency keys on the Stripe side plus idempotent DynamoDB writes on your side prevent duplicate charges from retries.

Why Pair Stripe With AWS Instead of Building Payments From Scratch

Stripe handles PCI compliance for card data, fraud signals, and global payment methods. AWS handles your application logic, order state, and business workflows. The integration boundary is a webhook and a couple of API calls — not a reason to touch raw card numbers.

Reference Architecture

Browser (Checkout) Stripe API Gateway /webhook Lambda Handler DynamoDB Secrets Manager

Step 1: Create a Checkout Session (Backend)

Never create the Checkout Session client-side with your secret key. Do it in a Lambda function behind API Gateway.

# create_checkout_session.py
import stripe
import boto3
import json
import os

secrets = boto3.client("secretsmanager")
stripe.api_key = secrets.get_secret_value(
    SecretId=os.environ["STRIPE_SECRET_ARN"]
)["SecretString"]

def lambda_handler(event, context):
    body = json.loads(event["body"])
    session = stripe.checkout.Session.create(
        mode="payment",
        payment_method_types=["card"],
        line_items=[{
            "price_data": {
                "currency": "usd",
                "product_data": {"name": body["product_name"]},
                "unit_amount": body["amount_cents"],
            },
            "quantity": 1,
        }],
        success_url="https://app.f9infotech.example/success",
        cancel_url="https://app.f9infotech.example/cancel",
        idempotency_key=body["order_id"],  # prevents duplicate sessions on retry
    )
    return {
        "statusCode": 200,
        "body": json.dumps({"url": session.url}),
    }

Step 2: Verify and Handle Webhooks

This is the step people skip, and it's the one that matters most.

# stripe_webhook_handler.py
import stripe
import boto3
import json
import os

secrets = boto3.client("secretsmanager")
endpoint_secret = secrets.get_secret_value(
    SecretId=os.environ["STRIPE_WEBHOOK_SECRET_ARN"]
)["SecretString"]

dynamodb = boto3.resource("dynamodb")
orders_table = dynamodb.Table("Orders")

def lambda_handler(event, context):
    payload = event["body"]
    sig_header = event["headers"].get("stripe-signature")

    try:
        stripe_event = stripe.Webhook.construct_event(
            payload, sig_header, endpoint_secret
        )
    except (ValueError, stripe.error.SignatureVerificationError):
        return {"statusCode": 400, "body": "Invalid signature"}

    if stripe_event["type"] == "checkout.session.completed":
        session = stripe_event["data"]["object"]
        # Idempotent write: PK = Stripe session id, safe against webhook retries
        orders_table.put_item(
            Item={
                "order_id": session["id"],
                "status": "paid",
                "amount": session["amount_total"],
            },
            ConditionExpression="attribute_not_exists(order_id)",
        )

    return {"statusCode": 200, "body": "ok"}
# Configure the webhook endpoint in Stripe CLI for local testing
stripe listen --forward-to https://abc123.execute-api.us-east-1.amazonaws.com/webhook

Step 3: Lock Down the Secrets

aws secretsmanager create-secret \
  --name stripe/secret-key \
  --secret-string '{"STRIPE_SECRET_KEY":"sk_live_xxx"}'

aws secretsmanager create-secret \
  --name stripe/webhook-secret \
  --secret-string '{"STRIPE_WEBHOOK_SECRET":"whsec_xxx"}'

Grant the Lambda execution role read-only access scoped to just these two secrets — not secretsmanager: on .

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "secretsmanager:GetSecretValue",
      "Resource": [
        "arn:aws:secretsmanager:us-east-1:123456789012:secret:stripe/secret-key-*",
        "arn:aws:secretsmanager:us-east-1:123456789012:secret:stripe/webhook-secret-*"
      ]
    }
  ]
}

PCI Scope: Why This Pattern Keeps It Small

Because card details are entered on Stripe-hosted Checkout (or client-side Stripe Elements, which tokenizes before anything reaches your server), your AWS infrastructure only ever handles tokens, session IDs, and webhook payloads — none of which are raw card data. This keeps you in SAQ A territory for PCI DSS instead of the much heavier SAQ D.

Idempotency and Reliability Checklist

  • Pass an idempotency_key on every Stripe API call that creates a resource.
  • Use a DynamoDB conditional write (or unique constraint) keyed on Stripe's event/session ID to prevent duplicate order fulfillment from webhook retries.
  • Always return HTTP 200 quickly from the webhook handler — Stripe retries on non-2xx, which can cause duplicate processing if your downstream logic isn't idempotent.
  • Log webhook failures to CloudWatch with enough context to replay them manually if needed.

FAQ

Do I need a PCI audit if I use Stripe Checkout? You still complete a Self-Assessment Questionnaire (SAQ A in most cases), but the scope and burden are far smaller than handling card data directly.

What happens if my Lambda webhook handler times out? Stripe will retry the webhook; your handler must be idempotent so a retry doesn't double-fulfill an order.

Should I use Stripe Elements or Checkout? Checkout is faster to implement and Stripe-hosted (less PCI scope and UI work); Elements gives more control over the payment UI while still keeping card data off your servers.

Can this architecture handle subscriptions, not just one-time payments? Yes — swap mode="payment" for mode="subscription" and handle additional webhook events like invoice.paid and customer.subscription.deleted.

Build a Secure Payments Pipeline With f9infotech

Payment integrations are unforgiving of small mistakes — a missed signature check or a non-idempotent webhook handler can mean duplicate charges or compliance exposure. f9infotech's AWS engineering team can design and harden your Stripe-on-AWS payment architecture.