Self-Service CI/CD for AWS

Self-service AWS CodePipeline platform — developers ship compliant CI/CD pipelines in minutes via dashboard, CLI, CDK, or AI prompt, while platform teams enforce policy-as-code guardrails, governance, and per-team isolation.

Billing Providers — Stripe & AWS Marketplace

Overview

Pipeline Builder charges through a pluggable billing provider, selected by BILLING_PROVIDER:

BILLING_PROVIDER Use it for Charging model
stub (default) Local dev / demos No real charges — subscriptions are created in-app with no external provider
stripe Direct SaaS billing you own Stripe Customers + Subscriptions; the app owns plans/prices and reconciles via webhooks
aws-marketplace Selling through AWS Marketplace Entitlements flow from AWS; add-ons report as metered usage (BatchMeterUsage)

Billing must be on (BILLING_ENABLED=true, the default) for any provider to serve plans. This page is the setup walkthrough for the two real providers. For what billing does once configured, see Billing Add-on Bundles, Billing Discounts, and the Environment Variables → Billing reference.

One provider per deployment. BILLING_PROVIDER is global. You do not run Stripe and Marketplace side by side — pick the one that matches how the deployment is sold.


Stripe

Stripe billing is direct: the app creates a Stripe Customer per organization and a Subscription per plan, using Price objects you create in Stripe. The app owns the reduction logic (discounts are customer-balance credits, never Stripe coupons — see Billing Discounts); Stripe owns the card, the invoice, and the payment. All state changes flow back through a signed webhook.

What you need

Step 1 — Select the provider

BILLING_ENABLED=true
BILLING_PROVIDER=stripe

Step 2 — Add your Stripe secret key

Stripe Dashboard → Developers → API keys → copy the Secret key (sk_test_… in test mode, sk_live_… in production):

STRIPE_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxxxxxxxx

STRIPE_SECRET_KEY is a secret — provision it via a sealed secret / SSM, never commit it.

Step 3 — Create Products & Prices in Stripe, then map them

In Stripe, create a Product + a recurring Price for every paid plan and every add-on you sell, one Price per interval (monthly and/or annual). The amount you set in Stripe must match the app’s configured price (Stripe is what actually charges the card). Copy each Price id (price_…).

Then wire them all up with STRIPE_PRICE_MAP — a single JSON object whose keys are <id>_<interval> (<id> is a plan id or a bundle id) and whose values are Stripe Price ids. interval is monthly or annual.

Plan prices (from the four tiers)

Plan id Monthly Annual Map keys
developer Free Free — (nothing charged)
pro $49 $490 pro_monthly, pro_annual
team $149 $1,490 team_monthly, team_annual
enterprise $599 $5,990 enterprise_monthly, enterprise_annual

The free developer tier needs no Price; the hidden unlimited tier is never sold. If a customer picks a plan/interval whose key is missing, subscription creation fails fast with No Stripe Price ID configured for plan "…" with interval "…" — so map every paid combination.

Add-on prices (from the existing bundles)

Add-ons are charged as extra subscription line items on the same subscription, so each sellable bundle also needs a Stripe Price per interval, keyed <bundleId>_<interval> in the same STRIPE_PRICE_MAP. Annual defaults to ~10× monthly:

Bundle id Monthly Annual Map keys
seat_pack $25 $250 seat_pack_monthly, seat_pack_annual
pipeline_pack $15 $150 pipeline_pack_monthly, pipeline_pack_annual
plugin_pack $15 $150 plugin_pack_monthly, plugin_pack_annual
api_pack $20 $200 api_pack_monthly, api_pack_annual
ai_pack $75 $750 ai_pack_monthly, ai_pack_annual
storage_pack $25 $250 storage_pack_monthly, storage_pack_annual
retention_pack $15 $150 retention_pack_monthly, retention_pack_annual
dora_history_pack $30 $300 dora_history_pack_monthly, dora_history_pack_annual
audit_log $20 $200 audit_log_monthly, audit_log_annual
sso $40 $400 sso_monthly, sso_annual
advanced_reporting $30 $300 advanced_reporting_monthly, advanced_reporting_annual
team_usage_analytics $30 $300 team_usage_analytics_monthly, team_usage_analytics_annual
compliance_standard $29.90 $299 compliance_standard_monthly, compliance_standard_annual
compliance_advanced $99.90 $999 compliance_advanced_monthly, compliance_advanced_annual

If a purchased bundle’s <bundleId>_<interval> key is absent from the map, its line item is silently skipped — the customer gets the entitlement but is never charged for it. Map every bundle you enable (see BILLING_BUNDLES_ENABLED), only for the intervals you sell.

Example (plans + a few add-ons)

STRIPE_PRICE_MAP='{
  "pro_monthly":"price_1AbcPro","pro_annual":"price_1AbcProYr",
  "team_monthly":"price_1DefTeam","team_annual":"price_1DefTeamYr",
  "enterprise_monthly":"price_1GhiEnt","enterprise_annual":"price_1GhiEntYr",
  "seat_pack_monthly":"price_1JklSeat","seat_pack_annual":"price_1JklSeatYr",
  "sso_monthly":"price_1MnoSso","sso_annual":"price_1MnoSsoYr"
}'

(Provide it as a single line — expanded here only for readability. Extend it with the remaining bundle keys from the table for every add-on you sell.)

Step 4 — Register the webhook

The app reconciles all subscription and payment state from Stripe webhooks. The endpoint is:

POST https://<your-public-host>/billing/stripe/webhook

Stripe Dashboard → Developers → Webhooks → Add endpoint → enter that URL, then select the events the app consumes:

Event Effect in Pipeline Builder
customer.subscription.created Records the subscription + grants the tier
customer.subscription.updated Re-syncs status/plan (status mapped internally; unpaid ⇒ canceled after grace)
customer.subscription.deleted Cancels the subscription, downgrades tier
invoice.payment_succeeded Marks paid / clears past-due
invoice.payment_failed Moves to past-due (grace period applies)
invoice.upcoming Drives renewal reminders + recurring-credit re-grant
charge.refunded Reverses the matching subscription
charge.dispute.created Reverses on dispute
invoice.voided / invoice.marked_uncollectible Reversal handling

Copy the endpoint’s Signing secret (whsec_…) into:

STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxxxx

The webhook route verifies every delivery against this secret over the raw request body and de-dupes redeliveries — so the URL must be publicly reachable through the nginx gateway, and STRIPE_WEBHOOK_SECRET must match the endpoint exactly or deliveries are rejected.

Step 5 — Test locally with the Stripe CLI

stripe login
# Forward live test events to your local gateway and print a whsec_ to use as STRIPE_WEBHOOK_SECRET:
stripe listen --forward-to https://localhost:8443/billing/stripe/webhook
# In another shell, simulate the lifecycle:
stripe trigger customer.subscription.created
stripe trigger invoice.payment_succeeded

Confirm the subscription appears (GET /billing/subscription for the org) and the tier is granted.

Step 6 — Go live

Swap test → live everywhere: STRIPE_SECRET_KEY=sk_live_…, a live-mode webhook endpoint with its own STRIPE_WEBHOOK_SECRET, and live Price ids in STRIPE_PRICE_MAP. Test-mode and live-mode objects never interoperate.

Stripe environment variables

Variable Default Description
STRIPE_SECRET_KEY Secret. Stripe API secret key (sk_test_… / sk_live_…). Required when BILLING_PROVIDER=stripe
STRIPE_WEBHOOK_SECRET Secret. Signing secret (whsec_…) for the endpoint at POST /billing/stripe/webhook; every delivery is signature-verified against it
STRIPE_PRICE_MAP {} JSON map of <id>_<interval> → Stripe Price id, where <id> is a plan id or a bundle id (e.g. {"pro_monthly":"price_…","seat_pack_annual":"price_…"}). A plan/interval absent here cannot be subscribed; a bundle absent here is granted but not charged

Stripe subscription statuses are mapped to internal statuses by a fixed table in the app (no env var); notably unpaidcanceled (Stripe sets unpaid only after the grace period), and unknown statuses fall back to incomplete.


AWS Marketplace

Under AWS Marketplace the customer subscribes on AWS, not in-app. AWS owns the contract and the bill; Pipeline Builder resolves the customer on redirect, reads entitlements to set the tier, listens to SNS for lifecycle changes, and reports add-on consumption as metered usage. Self-service in-app bundle purchase is disabled — entitlements flow from AWS.

What you need

Step 1 — Create the SaaS listing & dimensions

In the AWS Marketplace Management Portal, create the SaaS product and define its dimensions:

Note the product code AWS assigns.

Step 2 — Select the provider & product

BILLING_ENABLED=true
BILLING_PROVIDER=aws-marketplace
AWS_MARKETPLACE_PRODUCT_CODE=<your-product-code>
AWS_MARKETPLACE_REGION=us-east-1        # defaults to AWS_REGION, else us-east-1

Step 3 — Point the Fulfillment (registration) URL at the app

Set the product’s Fulfillment URL to a page in your frontend that captures the x-amzn-marketplace-token field (AWS POSTs it as a form field on redirect) and forwards it to the backend registration endpoint:

POST https://<your-public-host>/billing/marketplace/resolve
Body: { "x-amzn-marketplace-token": "<token>" }   (also accepts { "token": … })

The endpoint (no auth — it’s the AWS redirect target) runs the standard SaaS flow: ResolveCustomer on the token → look up any existing active subscription → GetEntitlements to determine the entitled tier → create the subscription. An entitlement with no in-map dimension falls back to developer.

Step 4 — Subscribe the SNS notification endpoint

AWS Marketplace publishes entitlement/subscription notifications to an SNS topic you own. Set its ARN and subscribe the app’s SNS webhook (HTTPS) to the topic:

AWS_MARKETPLACE_SNS_TOPIC_ARN=arn:aws:sns:us-east-1:<acct>:aws-mp-subscription-notification-<code>
POST https://<your-public-host>/billing/marketplace/sns

The endpoint confirms the SNS SubscriptionConfirmation handshake and verifies message signatures, then processes entitlement updates, cancellations, and reactivations (re-checking entitlements and updating the plan).

Step 5 — Grant IAM permissions

The billing service’s task role needs the Marketplace APIs it calls:

{
  "Effect": "Allow",
  "Action": [
    "aws-marketplace:ResolveCustomer",
    "aws-marketplace:GetEntitlements",
    "aws-marketplace:BatchMeterUsage"
  ],
  "Resource": "*"
}

Plus permission to receive from / confirm the SNS subscription for your topic.

Step 6 — Map dimensions

Three JSON maps connect AWS dimensions to the app’s plans, add-ons, and prices:

Variable Default Maps Purpose
AWS_MARKETPLACE_DIMENSION_MAP identity Marketplace tier dimension → local plan id Resolve the entitled tier from GetEntitlements
AWS_MARKETPLACE_BUNDLE_DIMENSION_MAP identity Add-on bundle id → metered dimension key Which dimension each add-on reports under
AWS_MARKETPLACE_DIMENSION_PRICE_MAP {} Metered dimension → cents per unit per cycle Drives credit drawdown; an unpriced dimension is reported in full

Tier dimensions (from the four plans)

Create one AWS entitlement dimension per paid tier. The free developer tier needs no dimension — an entitlement that resolves to nothing falls back to developer. If you name the AWS dimensions to match the plan ids, the identity default applies and you can omit AWS_MARKETPLACE_DIMENSION_MAP entirely; the explicit map is shown for clarity:

Plan id Monthly (default) Suggested AWS dimension Needs a dimension?
developer Free No — the fallback tier
pro $49 pro Yes
team $149 team Yes
enterprise $599 enterprise Yes
# Identity naming → this line is optional (it's the default):
AWS_MARKETPLACE_DIMENSION_MAP='{"pro":"pro","team":"team","enterprise":"enterprise"}'

Add-on dimensions (from the existing bundles)

Create one metered AWS dimension per add-on you sell. Map each bundle id → its AWS dimension name, and give each dimension its per-unit list price (cents) so credit drawdown values a withheld unit correctly. The rows below use every add-on that ships today, at its default monthly price:

Bundle id AWS dimension List price (default) Available tiers
seat_pack SeatPack $25 (2500) team, enterprise
pipeline_pack PipelinePack $15 (1500) all
plugin_pack PluginPack $15 (1500) all
api_pack ApiPack $20 (2000) all
ai_pack AiPack $75 (7500) all
storage_pack StoragePack $25 (2500) all
retention_pack RetentionPack $15 (1500) all (max 7)
dora_history_pack DoraHistoryPack $30 (3000) all (max 1)
audit_log AuditLog $20 (2000) pro
sso Sso $40 (4000) pro
advanced_reporting AdvancedReporting $30 (3000) developer, pro, team
team_usage_analytics TeamUsageAnalytics $30 (3000) pro, team
compliance_standard ComplianceStandard $29.90 (2990) developer, pro, team
compliance_advanced ComplianceAdvanced $99.90 (9990) developer, pro, team
AWS_MARKETPLACE_BUNDLE_DIMENSION_MAP='{"seat_pack":"SeatPack","pipeline_pack":"PipelinePack","plugin_pack":"PluginPack","api_pack":"ApiPack","ai_pack":"AiPack","storage_pack":"StoragePack","retention_pack":"RetentionPack","dora_history_pack":"DoraHistoryPack","audit_log":"AuditLog","sso":"Sso","advanced_reporting":"AdvancedReporting","team_usage_analytics":"TeamUsageAnalytics","compliance_standard":"ComplianceStandard","compliance_advanced":"ComplianceAdvanced"}'

AWS_MARKETPLACE_DIMENSION_PRICE_MAP='{"SeatPack":2500,"PipelinePack":1500,"PluginPack":1500,"ApiPack":2000,"AiPack":7500,"StoragePack":2500,"RetentionPack":1500,"DoraHistoryPack":3000,"AuditLog":2000,"Sso":4000,"AdvancedReporting":3000,"TeamUsageAnalytics":3000,"ComplianceStandard":2990,"ComplianceAdvanced":9990}'

Only list the add-ons you actually sell on Marketplace — a bundle with no dimension mapping isn’t metered, and a dimension with no price in AWS_MARKETPLACE_DIMENSION_PRICE_MAP is reported in full (never drawn against for credit). Tier availability (the “Available tiers” column) is enforced separately by BILLING_BUNDLE_<ID>_TIERS.

The price-map values are cents per metered unit per metering cycle (cycle = BILLING_METERING_INTERVAL_MS). The prices above are the monthly list defaults; if your metering cadence isn’t monthly, scale each value to the cycle. Either way, mirror your AWS listing’s dimension prices exactly — a wrong value directly mis-draws credit.

Step 7 — Enable metering (validate in dry-run first)

Add-on charges and usage-credit realization run on the metering cycle. Turn it on, but shadow it first:

BILLING_METERING_ENABLED=true
BILLING_METERING_INTERVAL_MS=3600000        # 1h; AWS BatchMeterUsage dedupes by (customer, dimension, hour)
BILLING_METERING_DRAWDOWN_DRYRUN=true        # compute + log intended withholding, report FULL quantities, touch nothing

Watch the logs for a cycle or two, confirm the intended dimensions/quantities match your listing, then set BILLING_METERING_DRAWDOWN_DRYRUN=false to go live.

Metering is default-off, and usage-credit discounts on Marketplace require both BILLING_DISCOUNTS_ENABLED and BILLING_METERING_ENABLED — a credit would otherwise bank but never reduce the AWS bill, so it’s rejected. See Billing Discounts → AWS Marketplace.

Step 8 — Verify

AWS Marketplace environment variables

Variable Default Description
AWS_MARKETPLACE_PRODUCT_CODE The Marketplace product code
AWS_MARKETPLACE_REGION AWS_REGION or us-east-1 Region for the Metering/Entitlement clients
AWS_MARKETPLACE_SNS_TOPIC_ARN SNS topic for entitlement/subscription notifications
AWS_MARKETPLACE_DIMENSION_MAP identity JSON map of Marketplace tier dimension → local plan id
AWS_MARKETPLACE_BUNDLE_DIMENSION_MAP identity JSON map of add-on bundle id → metered dimension key
AWS_MARKETPLACE_DIMENSION_PRICE_MAP {} JSON map of metered dimension → local list price in cents per metered unit per cycle
BILLING_METERING_ENABLED false Run the metering cycle (report add-on usage + realize credits). Off = no metering, and Marketplace credits are rejected
BILLING_METERING_INTERVAL_MS 3600000 Metering cycle cadence (1 hour)
BILLING_METERING_DRAWDOWN_DRYRUN false Shadow mode — compute + log intended withholding but report full quantities and leave balances untouched

Endpoints reference

Endpoint Auth Provider Purpose
POST /billing/stripe/webhook Signature Stripe Receives + verifies Stripe events (raw body)
POST /billing/marketplace/resolve None (AWS redirect) Marketplace Exchange a registration token for a subscription
POST /billing/marketplace/sns SNS signature Marketplace Entitlement/subscription lifecycle notifications
GET /billing/marketplace/entitlements Auth Marketplace Current entitlements for the account