Skip to main content

Automate SaaS Tenant Provisioning on AWS

Cloud ArchitecturePalaniappan P4 min read

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

  1. Confirm the tenant is supposed to be pool. If not, stop and use the isolation article.
  2. Implement the conditional put and the replay test before you create a secret.
  3. 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?
Skip it when you have not chosen pool, silo, or bridge. That decision is the other article. Also skip it when every tenant is a separate AWS account. Account vending is a different control plane. This workflow writes one item and one secret in an account you already operate.
What could go wrong if READY is set before the health check?
The product tells the customer to sign in, and the config the app reads is missing. Set READY only after the check returns. A failure marks FAILED and deletes the secret this execution created. It does not drop a schema other tenants use.
What if the same signup event arrives twice?
The first write is a conditional put on tenantId. If status is already READY, the execution stops without creating a second secret. If status is FAILED, a new authorised event may retry. An unauthorised duplicate must not.
Does this replace Cognito?
No. Cognito, or whatever issues the token, still authenticates the user. This workflow stores the tenant id your authorizer already expects. The Cognito article owns the sign-in design.
Palaniappan P
Palaniappan P

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.

AWS ArchitectureCloud MigrationGenAI on AWSCost OptimizationDevOps

Recommended Reading

Explore All Articles »
8 min

SaaS Multi-Tenancy on AWS: Silo vs Pool vs Bridge Model

Silo, pool, or bridge isolation isn't an architecture decision — it's a unit-economics decision you'll spend three years living with. A comparison framework for SaaS multi-tenancy on AWS, with the per-tenant cost trade-offs across scale tiers and the migration path for teams who picked wrong the first time.