Post-mortem: Checkout Session Expiry Parameter Mismatch
Post-mortem: Checkout Session Expiry Parameter Mismatch
Version: v1.0.108
Affected feature: Checkout Session creation (POST /api/v1/checkout/sessions)
Severity: Silent data loss — callers receive no error; session TTL is incorrect
Summary
A spec drift was identified between the Calmony Pay REST API and the SDK regarding how checkout session expiry is specified. The SDK accepts an expires_at Unix timestamp, while the REST API silently ignores expires_at and instead uses an expires_in integer (seconds). Callers relying on expires_at to control session lifetime received no error but got sessions with a default 30-minute TTL instead.
Background
The Calmony Pay REST API Contract (pinned spec) defines the checkout session creation payload as:
interface CreateCheckoutSessionParams {
// ... other fields ...
expires_at?: number; // Unix timestamp
}
The SDK client (src/lib/calmony-pay/client.ts) correctly exposes expires_at in its TypeScript types, consistent with the spec.
The Deviation
The REST API route handler at src/app/api/v1/checkout/sessions/route.ts uses a Zod schema (createCheckoutSessionSchema) that defines expiry as:
expires_in: z.number().int().min(300).max(86400).default(1800)
// seconds until expiry; range 300s–86400s; default 1800s (30 minutes)
The expires_at field is not present in this schema and is never read by the route handler. The API does not return a validation error when expires_at is passed — the field is simply stripped during parsing.
Behaviour comparison
| Caller action | Expected behaviour | Actual behaviour |
|---|---|---|
Pass expires_at: 1735000000 to REST API | Session expires at Unix time 1735000000 | expires_at silently ignored; session expires in 30 minutes |
Pass expires_in: 3600 to REST API | Session expires in 1 hour | ✅ Works correctly |
Pass expires_at via SDK | Forwarded to API, session expires at given timestamp | Forwarded but ignored by API; 30-minute TTL applied |
Impact
- No error is surfaced to callers — this is a silent failure.
- Sessions may expire earlier than intended (e.g. a session intended to last 2 hours expires in 30 minutes).
- Callers using the SDK's TypeScript types had no indication anything was wrong.
- Integrations built against the REST API directly using
expires_inare not affected.
Immediate Guidance
Until a fix is released that aligns the REST API and SDK:
-
If calling the REST API directly: Use
expires_in(an integer number of seconds, between300and86400) to control session TTL. Do not rely onexpires_at.POST /api/v1/checkout/sessions { "expires_in": 7200 } -
If using the SDK: Verify that your sessions are expiring at the expected time. If you have been passing
expires_at, your sessions are currently using the default 30-minute TTL. -
Audit existing integrations: Any checkout flow with a session window longer than 30 minutes should be treated as potentially broken until the fix is confirmed deployed.
Next Steps
A follow-up release will resolve the mismatch by aligning the REST API schema and the SDK to a single consistent expiry parameter. The resolution will be documented in the changelog when shipped.
Affected Files
src/app/api/v1/checkout/sessions/route.ts— REST API route; usesexpires_insrc/lib/calmony-pay/client.ts— SDK client; usesexpires_at