Testing
Verify subscription integrations against Throttle's hosted sandbox surfaces and your own staging storefront. Treat Throttle as an external API: configure keys, origins, webhooks, and assertions the same way you will in production.
Sandbox checklist
Before exercising a subscription flow, confirm the environment pieces below. The exact values come from your Throttle dashboard or onboarding handoff.
| Item | Where it belongs | What to verify |
|---|---|---|
| Secret API key | Merchant backend | Requests to Throttle are made server-side with a non-production environment key. |
| Application id | Merchant backend | Checkout-session requests target the sandbox application you intend to test. |
| Allowed origin | Throttle dashboard | Your staging storefront origin is allowlisted before mounting the iframe. |
| Webhook endpoint | Merchant backend | The endpoint is reachable by Throttle and verifies the webhook signature. |
| Return and cancel URLs | Checkout-session request | Both URLs point back to your staging storefront and handle terminal states. |
| Test payment method | Hosted checkout iframe | Use the sandbox card or payment method values provided for your account. |
THROTTLE_API_URL=https://api.usethrottle.dev
THROTTLE_CHECKOUT_ORIGIN=https://checkout.usethrottle.dev
THROTTLE_SECRET_KEY=sk_uat_...
THROTTLE_APPLICATION_ID=e7efb0a6-892e-46b2-97ab-296bb04c5b29
THROTTLE_WEBHOOK_SECRET=whsec_...Manual smoke test
A passing smoke test proves that your backend can create the session, the browser can mount the hosted payment surface, and your webhook endpoint can process durable subscription events.
- Open your staging pricing or subscribe page.
- Select a plan that creates a recurring checkout session.
- Confirm your backend creates the session with a recurring block.
- Mount the Throttle payment or checkout embed from the returned session.
- Complete the payment with sandbox payment details for your account.
- Confirm your storefront receives the success callback and renders the subscription confirmation state.
- Confirm your webhook endpoint receives
subscription.createdand any payment events needed by your ledger.
curl -X POST https://api.usethrottle.dev/api/v1/checkout/sessions \
-H "x-api-key: sk_uat_..." \
-H "content-type: application/json" \
-d '{
"applicationId": "e7efb0a6-892e-46b2-97ab-296bb04c5b29",
"externalCartId": "sub-test-001",
"returnUrl": "https://staging.shop.example.com/account",
"cancelUrl": "https://staging.shop.example.com/pricing",
"customer": {
"externalCustomerId": "user_test_001",
"email": "buyer@example.com"
},
"recurring": {
"plan": "pro_monthly",
"planName": "Pro Monthly",
"interval": "monthly",
"amount": 2999,
"trialDays": 14
},
"metadata": {
"testRunId": "sub-flow-2026-05-05"
}
}'Trial-abuse replay
To validate trial-abuse protection, repeat the subscription flow with the same physical card fingerprint and a different customer identity. The expected result is a subscription without the requested trial and a subscription.trial_blocked webhook.
| Step | Expected result |
|---|---|
| First subscription | Subscription is created with the requested trial window. |
| Second subscription with same card | Subscription is created without the trial window. |
| Webhook assertion | Receive subscription.trial_blocked with a reason such as card_already_used_for_trial. |
| Customer experience | Your storefront should explain that the buyer is subscribed without a new trial. |
Webhook assertions
Subscription state is durable only after your webhook handler processes signed events idempotently. Store event ids before applying side effects and treat delivery as at-least-once.
import { verifyThrottleWebhook } from '@/lib/throttle/webhooks';
export async function POST(req: Request) {
const rawBody = await req.text();
const signature = req.headers.get('x-throttle-signature');
if (!signature || !verifyThrottleWebhook(rawBody, signature, process.env.THROTTLE_WEBHOOK_SECRET!)) {
return new Response('Invalid signature', { status: 400 });
}
const event = JSON.parse(rawBody);
if (await db.webhookEvents.exists(event.id)) {
return Response.json({ ok: true });
}
await db.webhookEvents.insert({
id: event.id,
type: event.type,
receivedAt: new Date(),
});
if (event.type === 'subscription.created') {
await provisionAccess(event.data.subscription);
}
if (event.type === 'subscription.cancelled') {
await revokeAccess(event.data.subscription);
}
return Response.json({ ok: true });
}| Scenario | Events to assert |
|---|---|
| Subscription created | subscription.created plus the payment event your ledger expects. |
| Trial ends | subscription.activated |
| Renewal succeeds | subscription.renewed |
| Renewal fails | subscription.payment_failed, then subscription.past_due when the threshold is crossed. |
| Cancellation | subscription.cancelled with the expected data.reason. |
Automated test boundaries
Automated browser tests should validate your application's behavior around the embed: session creation, iframe mounting, event listeners, success routing, and webhook-driven state updates. They should not reach into the hosted payment iframe or depend on private Throttle implementation details.
| Layer | Test this | Avoid this |
|---|---|---|
| Unit tests | Your backend request builder, webhook signature verification, and idempotency. | Depending on undocumented Throttle behavior. |
| Browser tests | Your pricing page, terms gates, iframe container, postMessage handlers, and success state. | Clicking card fields inside the hosted payment iframe. |
| Integration smoke tests | A sandbox checkout from session creation through webhook side effects. | Assuming delivery order for webhook events. |
Common gotchas
- Origin not allowlisted. The iframe refuses to render until the parent storefront origin is configured for the merchant.
- Webhook signature mismatch. Verify against the webhook secret for the same merchant environment that sent the event.
- Duplicate webhook side effects. Throttle retries delivery; store event ids and make every handler idempotent.
- Expired checkout session. Create a fresh session for every buyer attempt instead of reusing old session ids.
- Testing inside the iframe. The payment surface is hosted and isolated. Assert the events it emits and the state your app persists.
Next
- Quickstart — create the first recurring checkout session.
- Subscription Webhooks — understand event payloads and delivery behavior.
- Trial Fraud Protection — see how trial eligibility is evaluated.