Stripe, Twilio & SDK Patterns
Stripe and Twilio are the most common paid SDKs in Node B2B APIs. This page covers timeouts, idempotency, test modes, and webhook pairing.
Search across all documentation pages
Stripe and Twilio are the most common paid SDKs in Node B2B APIs. This page covers timeouts, idempotency, test modes, and webhook pairing.
Quick-reference recipe card - copy-paste ready.
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: "2024-11-20.acacia",
timeout: 10_000,
maxNetworkRetries: 0,
});
const intent = await stripe.paymentIntents.create(
{ amount: 2500, currency: "usd", automatic_payment_methods: { enabled: true } },
{ idempotencyKey: req.header("Idempotency-Key") ?? undefined }
);When to reach for this:
// src/integrations/payments-service.ts
import Stripe from "stripe";
import twilio from "twilio";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: "2024-11-20.acacia",
timeout: 10_000,
maxNetworkRetries: 0,
});
const twilioClient = twilio(process.env.TWILIO_ACCOUNT_SID!, process.env.TWILIO_AUTH_TOKEN!);
export async function createPaymentIntent(
amountCents: number,
currency: string,
idempotencyKey: string
) {
return stripe.paymentIntents.create(
{ amount: amountCents, currency, automatic_payment_methods: { enabled: true } },
{ idempotencyKey }
);
}
export async function sendOtpSms(to: string, code: string) {
return twilioClient.messages.create({
to,
from: process.env.TWILIO_FROM_NUMBER!,
body: `Your code is ${code}`,
statusCallback: `${process.env.PUBLIC_API_URL}/webhooks/twilio/sms`,
});
}
// Express route
app.post("/payments/intent", async (req, res, next) => {
try {
const key = req.header("Idempotency-Key");
if (!key) return res.status(400).json({ error: "missing_idempotency_key" });
const pi = await createPaymentIntent(req.body.amountCents, "usd", key);
res.status(201).json({ clientSecret: pi.client_secret, id: pi.id });
} catch (err) {
next(mapStripeError(err));
}
});What this demonstrates:
| Operation | Pattern |
|---|---|
| One-time charge | PaymentIntent |
| SaaS billing | Customer + Subscription + Prices |
| Connect marketplace | stripeAccount header on requests |
| Refunds | Idempotent on refund create with key |
// Retrieve with expand - use sparingly
const sub = await stripe.subscriptions.retrieve(id, { expand: ["customer", "latest_invoice"] });payment_intent.succeeded, invoice.paid, customer.subscription.updatedevent.id - see Webhook Verification// Verify caller on inbound - TwiML webhook
app.post("/voice/inbound", express.urlencoded({ extended: false }), (req, res) => {
const twiml = new twilio.twiml.VoiceResponse();
twiml.say("Hello");
res.type("text/xml").send(twiml.toString());
});# Stripe test card 4242 4242 4242 4242
STRIPE_SECRET_KEY=sk_test_...
# Twilio magic numbers for SMS testidempotencyKey when enabling SDK maxNetworkRetriesclient_secret - payment leak in logs. Fix: log payment intent id only.apiVersion in code.| Alternative | Use When | Don't Use When |
|---|---|---|
| Paddle/Lemon Squeezy | Merchant of record | Custom Connect marketplace |
| MessageBird/Vonage | Twilio pricing/features | Already standardized on Twilio |
| Adyen/Braintree | Enterprise payment mix | Team knows Stripe |
| Resend + email OTP | Email codes only | SMS deliverability required |
Checkout hosted faster PCI scope. Elements custom UI - still use PaymentIntent server-side.
Verify API handles OTP lifecycle - less custom SMS code than raw Messages API.
Account Links API for Express/Standard connected accounts - separate webhook handlers.
PaymentIntent capture_method: manual for auth then capture less amount later.
Do not assume order. Use idempotent state machine on payment intent status.
Stripe handles 3DS flow client-side; listen for requires_action events.
Queue SMS sends; exponential backoff on 429; messaging service helps routing.
Good pattern - API enqueues, worker calls Stripe for fulfill after webhook.
Stripe ships types; Twilio types included - pin versions for consistency.
Community @nestjs/stripe or thin wrapper service - same idempotency rules.
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.
Reviewed by Chris St. John·Last updated Jul 16, 2026