# Webhook Event Processor — build spec

Build a small webhook event processor on LocalStack as three independent stacks, using
parallel agents that each work in isolation. Hand this file to an orchestrator agent
(for example, Claude Code running `claude-opus-5`) and let it coordinate the work.

## How to run this

Do everything through the **LocalStack MCP server** and its tools
(`localstack-management`, `localstack-ephemeral-instances`, `localstack-deployer`,
`localstack-aws-client`). Do not run Docker or Terraform by hand.

1. For each of the three stacks below (data, messaging, api), start a **separate
   sub-agent**. Give each sub-agent its own git worktree and its own **ephemeral
   instance** (create one with `localstack-ephemeral-instances` and keep the lifetime
   short). Each sub-agent writes, deploys (with `localstack-deployer`), and verifies
   only its own stack, on its own instance.
2. If a deploy leaves an instance in a bad state, the sub-agent should delete that
   instance and create a fresh one instead of debugging a polluted sandbox.
3. When all three stacks pass their smoke tests, start one more sub-agent on a fresh
   ephemeral instance, deploy all three together, and run the end-to-end test.
4. Delete every ephemeral instance when the run is finished.

## Shared conventions

- Region `us-east-1`. Use a plain provider block, `provider "aws" { region = "us-east-1" }`;
  the deployer injects the LocalStack endpoint.
- Resource names use the `webhook-` prefix.
- Stacks discover each other **at runtime** through SSM Parameter Store. Never hardcode
  another stack's ARNs or URLs. If a dependency parameter is missing, log and continue,
  so each stack still deploys and runs on its own.

| SSM key | Written by | Read by | Value |
|---|---|---|---|
| `/webhook/events-table` | data | messaging | DynamoDB table name |
| `/webhook/events-queue-url` | messaging | api | SQS queue URL |
| `/webhook/api-id` | api | end-to-end | REST API id |

## Stack: data

- DynamoDB table `webhook-events`, hash key `id` (String), `PAY_PER_REQUEST`.
- Publish `/webhook/events-table`.
- Smoke test: put an item into the table, then read it back.

## Stack: messaging

- SQS queue `webhook-events-queue` and a dead-letter queue `webhook-events-dlq`
  (`maxReceiveCount` 3).
- Python 3.12 Lambda `webhook-worker`, triggered by the queue (event source mapping,
  batch size 1). For each message: read `/webhook/events-table` from SSM and write
  `{ id, receivedAt, payload }`. If the parameter or table is missing, log and skip
  the write.
- Publish `/webhook/events-queue-url`.
- Smoke test: send a message to the queue, then confirm from the logs that the worker ran.

## Stack: api

- REST API Gateway `webhook-api`, stage `local`, resource `POST /events` (authorization
  NONE), AWS_PROXY integration to a Python 3.12 Lambda `webhook-ingest`.
- Ingest Lambda: parse the JSON body, assign an `id` if one is missing, read
  `/webhook/events-queue-url` from SSM, send the event to the queue, and return HTTP 202
  with `{ "id": ..., "queued": true|false }`. If the queue URL is missing, return 202
  with `"queued": false`.
- Publish `/webhook/api-id`.
- Smoke test: POST a JSON body to `<endpoint>/_aws/execute-api/<api-id>/local/events`
  and expect a 202 with an `id`.

## End-to-end

Deploy data, then messaging, then api on one instance. POST an event to the API and
confirm it appears in `webhook-events` within a few seconds
(api → queue → worker → table).
