Deadlines and expirations
Checkout sessions expire, card authorizations lapse, trials end, and invoices come due. Every clock the API runs is below, with the field that shows it, the event Flint sends when it passes, and what your integration should do.
How the API publishes a deadline#
A resource whose state changes at a time Flint knows publishes that time as an RFC 3339 timestamp in UTC. The field name tells you what happens at the deadline:
expires_at, or a qualified name such asrecovery_expires_at, means the thing lapses by itself and stops being usable.due_at, or a name such asevidence_due_at, means someone must act by then, and the resource continues either way. A deadline that is a calendar day is a_datefield instead.
Read the timestamp from the resource instead of adding a duration to a creation time. Several lifetimes depend on your settings, the request, or a card network.
Three kinds of lapse, and request clocks#
A clock on a resource is one of three kinds, and the kind decides whether an event arrives. Each row's "When it passes" cell starts with its kind: "Status changes:", "Computed at read:", or "Act by:". A fourth label, "Request:", marks the clocks on requests and credentials.
- A lapse that changes the stored status. Flint sends that status's event, like any other transition. A checkout session that becomes
expiredsendscheckout_session.expired. - A lapse computed when you read. The stored status doesn't change, so Flint sends no event. An API key reads
expiredonceexpires_athas passed. Schedule your own check from the field, and read the resource before you act on it. - An act-by deadline. Passing it changes nothing by itself. Flint sends a warning event before it only where you must act to avoid a loss, such as
subscription.trial_endingandinvoice.reminder_due.
Deadlines that a buyer acts on, such as a sign-in code, show up in Flint's emails and pages for the buyer, not in a webhook to you. The last section lists the request clocks, such as how long Flint waits for your webhook answer and how long it keeps an idempotency key. They aren't deadlines on a resource.
Checkout and payments#
| What runs out | How long | Field | When it passes | What to do |
|---|---|---|---|---|
| Checkout session | 24 hours after creation by default. Set it per session with expires_in_seconds, or for every session with settings.checkout.default_expires_in_seconds. | CheckoutSession.expires_at | Status changes: The session becomes expired and Flint sends checkout_session.expired. A payment already in progress, such as a 3D Secure screen, can still finish (see payment recovery). | Read expires_at instead of adding a duration to the creation time. Create a new session for a buyer who comes back late. Affirm needs at least 15 minutes left in an embedded session's payment window; inside the last 15 minutes the payment fails with PAYMENT_OPTION_UNAVAILABLE and reason action_window_too_short, so offer card. |
| Payment recovery after a session expires | Until recovery_expires_at, at most an hour after the payment attempt itself expires. | CheckoutSession.recovery_expires_at | Computed at read: Recovery ends. Flint sends no event. | Read the order with your API key to see whether the payment finished before you treat the session as lost. |
| Payment link sales | Until sales_end_at when you set one. An event link without it stops selling at event_at. | PaymentLink.sales_end_atPublicPaymentLink.sales_end_at | Computed at read: The link stops starting checkouts, and buyers see "Sales have ended" or your inactive_message. Flint sends no event. A buyer who opened checkout before the end has 15 minutes after it to start paying. | To keep selling, send a later sales_end_at, or null, in a PATCH to the link. |
| Buyer step on a payment, such as 3D Secure | 30 minutes after the attempt starts. | None | Status changes: Flint cancels the attempt's payments, the attempt ends canceled, and everything it held on the order is released. Each canceled payment sends payment_intent.canceled. | The API doesn't show this deadline yet, so read the attempt's status. To release the order sooner, cancel the attempt. |
| Card authorization under manual capture | Set by the card network, usually seven days for a card. Read it, never hardcode it. | PaymentIntent.authorization_expires_at | Status changes: The PaymentIntent moves to expired, the hold is released, and Flint sends payment_intent.expired. A capture sent afterward is refused with 409 ACTION_NOT_ALLOWED, and nothing is charged. | Capture before authorization_expires_at, or cancel the hold. Store the timestamp with the order or booking the hold is for. |
| Refunds on Affirm payments | 120 days after the payment settles. | None | Computed at read: POST /v1/refunds returns 409 ACTION_NOT_ALLOWED with reason refund_unavailable. Flint sends no event. | Refund within 120 days of settlement. After that, reimburse the buyer another way. |
| Card saved at checkout with a mobile phone number | The buyer has 24 hours after the payment succeeded or was approved to confirm it. | CheckoutPaymentMethodSave.expires_at | Status changes: The card becomes failed, and no one can pay with it. Flint sends no event today. | Wait for payment_method.activated before you charge a card saved this way. |
| Checkout sign-in code | 15 minutes for an emailed code. 10 minutes after the text provider first sends a texted code. | CheckoutSessionCustomerVerification.expires_at | Computed at read: The code stops working. Flint sends no event, because the buyer is the one who acts on it. | Show the buyer a way to request a new code. A code also allows 5 tries, and a new request replaces the earlier emailed code. |
| Promotion code | Until expires_at when you set one. | PromotionCode.expires_at | Computed at read: The code stops being redeemable. Flint sends no event. | To extend the offer, update the code's expires_at. |
| Gift card reservation you created without capturing | 15 minutes by default, or until the expires_at you set, up to 24 hours. | GiftCardRedemption.expires_at | Status changes: The redemption becomes expired, the reserved value goes back to the card, and Flint sends gift_card_redemption.updated. A reservation held for a payment attempt follows that attempt instead. | Capture the redemption when the sale succeeds, or cancel it when the sale fails. |
| Redirects from an archived custom domain | 30 days after you archive the domain, until redirect_expires_at. | CustomDomain.redirect_expires_at | Computed at read: Redirects from the hostname end, and links on it stop working. Flint sends no event, though archiving sent custom_domain.archived. | Delete the records in dns_records once you no longer need the redirects, and replace the hostname in shared links, printed QR codes, and invoice links before the window ends. |
| Public payment link read, for starting a checkout | Start the checkout within 24 hours of the read, by resolution_context_start_deadline_at. The context credential itself ends at resolution_context_expires_at. | PublicPaymentLinkResult.resolution_context_expires_atPublicPaymentLinkResult.resolution_context_start_deadline_at | Computed at read: Starting the checkout returns 409 VERSION_CONFLICT, and no checkout is created. Flint sends no event. | Read the public link again and send the new resolution_context. |
Stock and delivery#
| What runs out | How long | Field | When it passes | What to do |
|---|---|---|---|---|
| Inventory reservation hold | 15 minutes by default, and at most 24 hours from now. | InventoryReservation.expires_at | Status changes: Flint releases whatever is still held and sends inventory_reservation.hold_expired. Committed quantity stays. | To give a buyer more time, send a new expires_at in a PATCH before the hold lapses. A lapsed hold can't be extended, so create a new reservation. |
| Delivery quote | 24 hours when every method uses fixed, tiered, or rate-table pricing. At most 15 minutes otherwise, and under 5 minutes for a cart with tracked inventory. | DeliveryQuote.expires_at | Computed at read: The quote is no longer valid. Flint sends no event. | Quote again with the buyer's current cart and address, then select an option from the new quote. |
| Provisional delivery selection | Until expires_at. Its calculated pricing is good until calculation_expires_at. | DeliverySelection.expires_atDeliverySelection.calculation_expires_at | Computed at read: The selection can no longer be committed, and the calculated pricing is no longer valid. Flint sends no event. | Select the option again before the buyer pays. |
| Choosing a delivery or pickup window | Until the window's expires_at, the last moment the buyer can choose it. For a schedule_window method that is the preparation lead time before the window starts, or the same-day cutoff when that is earlier. | DeliveryWindowResource.expires_at | Computed at read: The buyer can no longer choose that window. Flint sends no event. | Offer only windows whose expires_at is still ahead, and quote again for current windows. |
Billing#
| What runs out | How long | Field | When it passes | What to do |
|---|---|---|---|---|
| Invoice payment | Through the invoice's due_date, set from its payment terms or a date you choose. It is overdue once that date ends in the invoice's time zone. | Invoice.due_date | Act by: Flint sends invoice.overdue while a balance remains, and is_overdue is true when you read the invoice. If the payment term carries a late fee, invoice.late_fee_due follows once the grace period ends. Before the due date, Flint sends invoice.reminder_due on your reminder cadence. | When delivery_mode is caller_managed, send the reminder yourself on invoice.reminder_due. To charge a late fee, assess it after invoice.late_fee_due. |
| Subscription trial | Until trial_end_at. | Subscription.trial_end_at | Status changes: Flint charges the first period and the subscription stays trialing while the charge is pending. A successful charge makes it active and sends subscription.payment_succeeded and subscription.activated. A declined charge moves it straight to past_due, and the retry that succeeds sends subscription.activated once. Before the trial ends, Flint sends subscription.trial_ending. | Grant paid access once subscription.activated arrives, and ask for a new card on subscription.past_due. |
| Subscription renewal | At next_billing_at. | Subscription.next_billing_at | Status changes: Flint creates the cycle's order and charges the saved payment method. Success sends subscription.payment_succeeded and order.paid for the order. A failed charge sends subscription.past_due. Before the renewal, Flint sends subscription.renewal_upcoming, three days ahead, or 30 days ahead for a subscription that renews every 12 months or more. | Renew access on subscription.payment_succeeded, and keep next_billing_at for your own reminders. |
| Failed-payment retry | At next_retry_at. Flint retries on days 1, 4, 9, and 16 by default. | Subscription.next_retry_at | Status changes: Flint retries the charge. A retry that succeeds sends subscription.payment_succeeded and the subscription is active again. A failed automatic retry sends no event, and when every retry fails Flint sends subscription.dunning_exhausted and applies the end action. | Ask the customer for a working card after subscription.past_due, and decide access when subscription.dunning_exhausted arrives. |
| Scheduled cancellation | At scheduled_cancel_at: the trial's end while trialing, the period's end while active, or next_retry_at while past_due. | Subscription.scheduled_cancel_at | Status changes: The subscription ends and Flint sends subscription.canceled. Scheduling it sent subscription.cancellation_scheduled. | Revoke access on subscription.canceled, not at scheduled_cancel_at. |
| Scheduled resume after a pause | A pause with a length resumes at scheduled_resume_at, that many billing intervals after paused_at. | Subscription.scheduled_resume_at | Status changes: Flint applies the resume and sends subscription.resumed. The status can be past_due while an overdue period is collected. | Grant paid access only when the status is active. |
| Scheduled invoice issue | At scheduled_issue_at, a future time you set on a draft. | Invoice.scheduled_issue_at | Status changes: Flint issues and emails the invoice and sends invoice.issued. If the issue fails, the invoice stays a draft, scheduled_issue_at is cleared, and Flint sends invoice.issue_failed. | Set recipient_email on the draft, and watch for invoice.issue_failed. |
| Your Flint billing invoice | Until the invoice's due_at. Processing pauses 21 days after the due date, or later if your billing terms allow more time, when the amount is still unpaid. | MerchantBillingInvoice.due_at | Act by: In live mode, Flint notifies your account owners, admins, and confirmed notification contacts one day after the due date, and again seven days before the pause. If the amount is still unpaid, processing pauses, payments and payouts read blocked with status_reason billing_suspended, and Flint sends merchant.readiness_updated. | Keep enough in your Flint balance to cover what you owe, and read GET /v1/merchant-billing-balances for the amount. |
Disputes#
| What runs out | How long | Field | When it passes | What to do |
|---|---|---|---|---|
| Dispute evidence | Until evidence_due_at. There is none when the dispute has no response window, such as a bank return. | Dispute.evidence_due_at | Act by: Once evidence_due_at has passed, reads show is_evidence_deadline_passed: true, is_action_required: false, and no accept in supported_actions. Webhook payloads keep the values from when Flint recorded the change. Flint sends no event. | Respond or accept before evidence_due_at. Subscribe to dispute.needs_response to learn when action is required. |
Files and downloads#
| What runs out | How long | Field | When it passes | What to do |
|---|---|---|---|---|
| Upload URL | 15 minutes after the file is created. | FileUpload.expires_atFile.expires_at | Computed at read: The file's status reads expired and the signed upload URL stops working. Flint sends no event, because files send no webhooks. | Upload within 15 minutes, or create a new file. |
| Unattached ready file | 24 hours after processing, or 7 days after its last detachment. | File.expires_at | Computed at read: The file's status reads expired. Flint sends no event, because files send no webhooks. | Attach a ready file before expires_at, and upload it again if it lapsed. |
| Report download link | 24 hours after the report succeeds. | PublicDownload.expires_at | Computed at read: The download stops working. The report keeps its succeeded status, and Flint sends no event. | Download the file soon after report.succeeded. To get it again, create a new report. |
Credentials and sessions#
| What runs out | How long | Field | When it passes | What to do |
|---|---|---|---|---|
| API key with an expiry | Until the expires_at you set. A key without one never expires. | APIKey.expires_at | Computed at read: The key stops authenticating and reads expired. Flint sends no event. | Set a short expires_at for a contractor, a migration script, or a load test, and rotate other keys before they lapse. |
| Customer session secret and refresh token | The secret lasts 1 hour by default (5 minutes to 24 hours). The refresh token lasts 7 days by default (1 hour to 30 days), and refreshing doesn't extend it. | CustomerSession.expires_atCustomerSession.refresh_token_expires_at | Computed at read: The secret stops authenticating account calls. When refresh_token_expires_at passes, the buyer signs in again. Flint sends no event. | Refresh before expires_at. After refresh_token_expires_at, create a new session. |
| Customer access link | 15 minutes, and it opens once. | AccessLink.expires_at | Computed at read: The link stops opening the resource or signing the buyer in. Flint sends no event. | Redirect the buyer at once, and create a new link when they come back. |
| Onboarding email code | 15 minutes after it is issued. | OnboardingStartResult.expires_at | Computed at read: The verification_token stops working. Flint sends no event. | Keep the token on your backend, and start email verification again for a new code. |
| Onboarding session | 24 hours, until onboarding_session_expires_at. It can't be refreshed. | OnboardingVerifyEmailResult.onboarding_session_expires_at | Computed at read: The onboarding session token stops working. Flint sends no event. | Verify the email again to get a new session token. |
| First API key after verifying the email | Within 5 minutes of verifying the email. | None | Computed at read: Issuing a key that holds accounts.api_keys.write returns 403 IDENTITY_VERIFICATION_REQUIRED. Flint sends no event. | Issue the first key right after verifying, or verify the email again. |
| Merchant account session launch token | Until launch_token_expires_at. | MerchantAccountSession.launch_token_expires_at | Computed at read: The launch token stops working. Flint sends no event. | Create a new merchant account session. |
| Onboarding requirements deadline | Until due_at on the merchant's readiness requirements. | ReadinessRequirements.due_atResponseWarning.due_at | Act by: Information still due moves to past_due, and payments readiness reads blocked with status_reason requirements_past_due. Readiness changes send merchant.readiness_updated. | When merchant.readiness_updated arrives, read GET /v1/merchant and follow the next actions it lists. |
| Previous signing secret after a rotation | 24 hours by default. For a webhook endpoint, set expire_previous_secret_at up to 7 days out. | WebhookEndpoint.previous_secret_expires_atPartnerApp.previous_secret_expires_at | Computed at read: The replaced secret stops signing, and deliveries carry one signature from the new secret. Flint sends no event. | Deploy the new secret before previous_secret_expires_at, then remove the old one. |
| API version rollback offer | 72 hours after you move to a newer version. | Merchant.api_version_rollback_expires_atWebhookEndpoint.api_version_rollback_expires_atPartnerApp.api_version_rollback_expires_at | Computed at read: api_version_previous clears and the rollback offer ends. You can still set any supported version. Flint sends no event. | To roll back, send the api_version_previous value as api_version before the offer ends. |
| Recovering a new gift card code | 24 hours after the card is created or its code is rotated. | CreateGiftCardResult.code_recovery_expires_atRotateGiftCardCodeResult.code_recovery_expires_at | Computed at read: A replay with the same Idempotency-Key stops returning the code. Flint sends no event. | Store the code as a credential when you create or rotate it. |
Webhooks and requests#
| What runs out | How long | Field | When it passes | What to do |
|---|---|---|---|---|
| Answering a webhook delivery | 10 seconds per attempt. | None | Request: The attempt fails as webhook_timeout, and Flint retries on its schedule. | Return a 2xx within 10 seconds and do the real work in a background job. |
| Webhook delivery retries | Up to 9 automatic attempts over about 3 days. The ninth lands 71 to 86 hours after the first. | WebhookDelivery.next_retry_at | Request: Flint makes the next attempt. After the ninth failed attempt the delivery is failed, and you can resend it. | Deduplicate deliveries before you process them, and resend failed ones after you fix the endpoint. |
| Delivery JWT | 5 minutes: exp is iat plus 300 seconds. | None | Request: A token past exp fails verification. | Check iat and exp with at most 60 seconds of leeway, and reject a token whose lifetime exceeds 300 seconds. |
| Event payload retention | 14 days. The records themselves are deleted after 90 days. | None | Request: The event keeps its metadata and drops data. A pending delivery stops retrying, and a resend returns 409 ACTION_NOT_ALLOWED with reason payload_expired. | Read the resource with GET for its current state. |
| Idempotency key | 24 hours from the original request on routes where the key is optional. On routes that require it, as long as the command's effects exist, except POST /v1/customer-sessions/refresh, which holds it for 24 hours. | None | Request: On an optional route, the same key runs as a new request. A required route returns the original result however late the retry arrives. | Keep automated retries well inside 24 hours, and never reuse an old key for a new action. |
| Rate limit | Wait the number of seconds in the Retry-After header. | None | Request: The next request can succeed. A 429 RATE_LIMIT_EXCEEDED means the request didn't run. | Wait that long, then send the same request with the same Idempotency-Key. |
| Gift card challenge proof | 5 minutes after it is issued, for one checkout session and one use. | None | Request: Flint rejects the apply request with GIFT_CARD_CHALLENGE_REQUIRED and reason proof_rejected. | Remount the challenge frame for a new proof before each apply request while a challenge is required. |
| Command line sign-in | Access tokens last 15 minutes and refresh on their own. A session ends after 30 days without a refresh, or 90 days after sign-in, whichever comes first. | OAuthTokenResponse.expires_in | Request: The session ends, and the CLI needs a new sign-in. | For unattended jobs, use an API key instead of a signed-in session. |
