Automate SaaS Tenant Provisioning on AWS
Quick summary: Assume 8 state transitions and 200 approved signups in a month: 1,600 transitions. Step Functions Standard includes 4,000 free transitions a month in US East (N. Virginia) at $0.000025 after that. The workflow marks READY only after the health check.
Key Takeaways
- Assume 8 state transitions and 200 approved signups in a month: 1,600 transitions
- Step Functions Standard includes 4,000 free transitions a month in US East (N
- Virginia) at $0.000025 after that
- On 19 October 2026, the slow part of a new SaaS customer is still someone creating the tenant by hand, then forgetting the secret
- You do not get a new account, a new VPC, or a silo database
Table of Contents
On 19 October 2026, the slow part of a new SaaS customer is still someone creating the tenant by hand, then forgetting the secret. The silo, pool, and bridge post chooses the isolation model. The pattern is the reference. This page starts after that choice, and only for a pool: one shared table, one item per tenant. Shard-key pain on an existing pool is the Aurora Limitless decision.
Reproduce this — Copy the provision states. If the sheet’s first row is not a conditional put, you do not have idempotency yet.
Who this is for
The platform engineer who gets a Slack message that says the contract is signed, and the CTO who wants that message to become a tenant without a console session.
What you get, and what you do not
You get one item, one secret, a health check, and a status. You do not get a new account, a new VPC, or a silo database. You do not get a user who can sign in. Cognito for SaaS is that problem.
Opinionated take: pool item plus secret, Standard workflow, READY last. The trade-off is that a tenant who contracted for a dedicated database is out of scope. Sell them the isolation article’s answer, then a different workflow.
Architecture
Your billing or signup system emits a custom EventBridge event only after a person approves the tenant. Step Functions Standard runs the states on the sheet. Express is the wrong type: you want exactly-once workflow execution and a history you can read when a signup fails at 2 a.m. The welcome page states that Standard workflows are exactly-once and can run for up to a year. Express is at-least-once and stops at five minutes.
flowchart LR
approved[SignupApproved] --> workflow[StepFunctionsStandard]
workflow --> item[ConditionalPut]
item --> secret[SecretsManager]
secret --> health[HealthCheck]
health --> ready[StatusReady]
health --> failed[StatusFailed]Walkthrough
1. Accept only an authorised event. The event carries tenantId, a plan code, and an approver id. Reject a missing approver before any write. The event pattern is yours. AWS does not define a tenant-signup schema.
2. Conditional put. Context: DynamoDB PutItem, condition expression attribute_not_exists(tenantId).
{
"TableName": "tenants",
"Item": {
"tenantId": { "S": "ten_01" },
"status": { "S": "RECEIVED" }
},
"ConditionExpression": "attribute_not_exists(tenantId)"
}On ConditionalCheckFailedException, read the item. READY means stop as success. Anything else is a conflict a person looks at. Do not overwrite READY.
3. Create the secret with a name that includes the tenant id, so a retry can see the name. The resource policy, if you use one, allows only the application role that already assumes tenant context. Do not put the secret value in the state payload. Pass the ARN.
4. Health check, then READY. The check reads the item and the secret ARN and calls whatever “ready” means for your app: a row exists, the secret resolves. Only then update status to READY.
5. Compensate. If the secret create succeeds and the health check fails, delete that secret and set FAILED. If the conditional put never succeeded, delete nothing. Do not run a shared-schema drop.
IAM
The workflow role can dynamodb:PutItem and dynamodb:UpdateItem on the tenants table, secretsmanager:CreateSecret and secretsmanager:DeleteSecret on the name prefix you use, and secretsmanager:GetSecretValue only for the health-check function. iam:PassRole is not required if you are not creating roles. Do not grant secretsmanager:DeleteSecret on *.
Failure and retries
Retry CreateSecret on throttling. Do not retry a conditional-check failure. A replayed event is handled by the read-after-conflict branch. Record the execution ARN on the item so support can open one history.
Cost, as an assumption
Step Functions pricing in US East (N. Virginia): $0.000025 per state transition, 4,000 free per month. Assume 8 transitions and 200 signups: 1,600 transitions, inside the free tier. Secrets Manager has its own per-secret monthly charge. Price that from the Secrets Manager pricing page. Two hundred secrets are the bill that matters, not the state machine. Not a measured saving.
Testing
Send one event. Expect READY and one secret. Send it again. Expect no second secret. Force the health check to fail. Expect FAILED and no leftover secret from that execution. Send an event with no approver id. Expect no item.
What broke — Lab, 19 October 2026. Not a customer account. The health check treated a DynamoDB 200 as ready before the secret write had been read, because the check ran in parallel with the create. Detection: the app logged a missing secret for a READY tenant. Recovery: put the check after the create, in series, and read the secret. Lesson: parallel “it exists” checks race the write.
Production checklist
- Isolation model is already written down. This workflow says pool.
- Approver id is required.
- READY is the last write.
- Compensation deletes only the secret this execution created.
- State payloads do not contain secret strings.
- A replay of a READY tenant is a no-op.
What This Post Doesn’t Cover
Silo and bridge provisioning. Per-tenant KMS keys. Cognito user pools. Offboarding. That workflow is the retention article.
What to Do This Week
- Confirm the tenant is supposed to be pool. If not, stop and use the isolation article.
- Implement the conditional put and the replay test before you create a secret.
- Add the health check only after replay is boring.
Running this for a customer is managed services work when the signup path already exists and the gap is the workflow, not a new product.
Frequently asked questions
When should we not automate provisioning this way?
What could go wrong if READY is set before the health check?
What if the same signup event arrives twice?
Does this replace Cognito?

AWS Cloud Architect & AI Expert
AWS-certified cloud architect and AI expert with deep expertise in cloud migrations, cost optimization, and generative AI on AWS.


