Skip to content

Customer accounts

Your buyers already have an account.

The moment someone pays you, they can find the order, track the parcel, download the invoice, change the card, pause the subscription, and start a return, at account.withflintpay.com, with your receipts already linking to it. Put your name and colors on it in one call. Move it to account.yourbrand.com with two DNS records. Or build your own on a buyer-scoped API where the credential decides whose data comes back. Every question that would have been a support ticket is a button the buyer presses.

8

things a buyer does for themselves, from the first payment

each a screen in the account and a route under /v1/me

10

resource families under /v1/me, one buyer-scoped credential

counted from the OpenAPI spec

6

email families you can take over, one at a time

customer_email_delivery

01Self-serve

Every support ticket, answered before it is sent.

After paying, buyers ask the same eight questions. Each one is a screen in the account and a route under /v1/me, so the answer is the same whether Flint hosts the account or you do. The buyer presses the button, the record changes, and you hear about it on your webhook.

GET /v1/me/shipments

Where is my order?

Order history, every parcel with its carrier and tracking link, pickup windows, and download links, on one receipt page.

POST /v1/me/orders/{order_id}/send-receipt

Send me the receipt again

The receipt re-sends itself from the order page, and an invoice PDF downloads from the invoice.

POST /v1/me/payment-methods

My card expired

The buyer adds a card, makes it the default, or removes one. Subscriptions bill the new card from the next charge.

POST /v1/me/subscriptions/{subscription_id}/pause

Pause my subscription

Pause, resume, cancel at period end, reverse a scheduled cancellation, or swap the card the subscription bills.

POST /v1/me/returns

I want to return this

A preview says what is eligible, the return opens, the label and the refund follow your return policy.

POST /v1/me/invoices/{invoice_id}/checkout-session

Pay this invoice

Open invoices list with the amount due now, and the pay button opens a checkout the invoice owns.

POST /v1/me/addresses

I moved

A saved address book with a default for billing and shipping, prefilled into the next hosted checkout.

POST /v1/me/email-change-requests

Change my email

Codes go to the old address and the new one, and nothing moves until both are confirmed.

The Stripe customer portal shows invoices, so one-time Checkout payments without one don’t appear there; the lookup on Stripe customer portal order history explains how to show them.

The dunning email lands here, and the buyer fixes it.

When a renewal fails, Flint emails the customer and links them to the subscription, where they see what failed and change the card without opening a ticket. The next retry bills the new card, and you learn about it from a webhook, not from your inbox.

The subscription page while it is past due. Every merchant has this at account.withflintpay.com on day one, under your brand or your own domain.

A return, start to refund

Buyer opens CS-1042 and picks the dripperMon
Your policy approves it and emails the labelMon
Parcel received and inspectedThu
$30.00 refunded to the original cardThu

What you hear while they do it

  • payment_method.saved
  • subscription.payment_succeeded
  • subscription.paused
  • return.created
  • customer.updated

Every self-serve action is an ordinary event on your webhook stream, with the same payload as the merchant-side call. Your fulfillment, billing, and support tools already listen to it. Buyer-initiated returns →

02Your brand

Your name, your colors, your type. One call.

One settings call restyles the account, the hosted checkout, and every email Flint sends your buyers, because a customer meets all three and they should look like one company. Left is the account as it ships. Right is the same account after the call below, rendered from the values in the request.

account.withflintpay.com
Flint's default: Flint's mark, Flint's palette, the credit in the footer.
account.withflintpay.com
After the call: Cedar & Stone's name and mark, its green, its serif, 4px corners, and Flint's credit in the footer.
the whole restyle
curl -X PATCH https://api.withflintpay.com/v1/settings \
  -H "Authorization: Bearer $FLINT_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "branding": {
      "primary_color": "#1B4D3E",
      "background_color": "#F4EFE5",
      "text_color": "#1A1714",
      "font_family": "system_serif",
      "corner_radius": 4
    },
    "customer_account": {
      "mode": "flint_hosted",
      "presentation": { "account_name": "Cedar & Stone" }
    }
  }'

Colors, typeface, and corner radius on branding; the account name on the account's presentation. Both in one PATCH.

  • primary_color
  • accent_color
  • background_color
  • text_color
  • font_family
  • corner_radius
  • account_name

Four typefaces, four colors, a radius from 0 to 32 pixels, and the name over the door, with a small Powered by Flint credit in the footer. Deep enough that the account reads as yours, and shallow enough that Flint keeps shipping new screens into it without breaking your theme. Customer accounts guide →

03Your domain

account.yourbrand.com, certificate included.

A custom domain costs $15 a month. After you activate it in Billing, setup is one settings call and two DNS records. Flint issues the certificate, serves the account from your hostname, and re-checks it on a schedule. Buyer links keep working the whole time, because they resolve when the buyer clicks, not when the email was sent.

set the hostname
curl -X PATCH https://api.withflintpay.com/v1/settings \
  -H "Authorization: Bearer $FLINT_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "customer_account": {
      "mode": "flint_hosted",
      "presentation": { "custom_domain": "account.cedarandstone.com" }
    }
  }'

One exact subdomain. It has to be a hostname you already registered as a payment method domain, the same registration that prepares domain-bound wallet payments, which is how Flint knows it is yours.

then read back the records
{
  "data": {
    "customer_account_domain_status": {
      "hostname": "account.cedarandstone.com",
      "domain_status": "provisioning",
      "dns_records": [
        {
          "dns_record_type": "cname",
          "name": "account.cedarandstone.com",
          "value": "account.withflintpay.com"
        },
        {
          "dns_record_type": "txt",
          "name": "_acme-challenge.account.cedarandstone.com",
          "value": "example-validation-token"
        }
      ],
      "last_checked_at": "2026-08-10T17:00:00Z"
    }
  }
}

Flint returns exactly what to publish, on the settings object, from the moment provisioning starts. Publish them DNS-only and the status moves on its own.

"domain_status": "provisioning"

provisioning

The records are out and the certificate is being issued. Account links stay on Flint's domain until it is ready.

"domain_status": "active"

active

Buyers land on your hostname. Nothing else about the account changed, and every link Flint resolves now points there.

"domain_status": "attention_required"

attention_required

A record stopped matching. Links go back to Flint's domain on their own, and the response says which record to fix.

A buyer link never breaks over DNS. Every account link Flint writes into a receipt, a shipping notice, or a dunning email is resolved at click time against your current settings. While the hostname is provisioning, links use Flint's domain. When it is active, they use yours. If it ever stops resolving, they go back to Flint's domain until it recovers, and a buyer clicking a three-week-old receipt still reaches their order. What you gain is the branded address. What you never risk is the buyer's access.

  • custom_domain
  • customer_account_domain_status
  • domain_status
  • dns_records
  • last_checked_at

Payment method domains → · Custom domain setup →

04Or build it

Build your own. Flint keeps the ownership check.

Authenticate the buyer with your own login, mint a customer session on your backend, and read /v1/me with it. There is no customer_id anywhere in that namespace, so a front end you build cannot ask for the wrong buyer's data, even by mistake. You write the screens. Flint decides whose data comes back.

after your own login
curl -X POST https://api.withflintpay.com/v1/customer-sessions \
  -H "Authorization: Bearer $FLINT_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "customer_id": "cus_1kmn0aExample" }'

Your backend authenticates the buyer however it already does. Flint never sees your login form, your password, or your identity provider.

a credential for one buyer
{
  "data": {
    "customer_session_id": "cses_1kmn0aExample",
    "customer_id": "cus_1kmn0aExample",
    "secret": "flint_cses_example",
    "expires_at": "2026-08-10T18:00:00Z",
    "refresh_token": "flint_cref_example",
    "refresh_token_expires_at": "2026-08-17T17:00:00Z"
  }
}

A working secret, good for an hour by default, and a rotating refresh token good for seven days. Both stay on your server, and the lifetimes are fields on the request.

the buyer's orders
curl https://api.withflintpay.com/v1/me/orders \
  -H "Authorization: Bearer flint_cses_example"

No customer filter, because there is nothing to filter by. The credential already decided.

Sending a customer_id anywhere under /v1/me, in the query or nested in a body, is rejected with ME_CUSTOMER_ID_FORBIDDEN rather than quietly ignored, so a caller can never believe they scoped a request when they did not. A resource that exists but belongs to another buyer returns a 404, not a 403: the namespace will not even confirm that someone else's order id is real.

That is the difference between this and a filtered merchant key. A bug in your front end cannot widen the query, because the query has no width to widen. Reads are trimmed to what a buyer should see and writes to what a buyer should be able to do, and money still moves through a checkout session the buyer is redirected to, so an account you build cannot charge a card directly, in the same way Flint's cannot.

Refresh rotates both credentials

Trade the refresh token for a new pair with no API key on the call, so the code that keeps a buyer signed in never holds your secret key. Every refresh retires the token it consumed.

A stolen token ends the family

Presenting a refresh token that was already exchanged returns CUSTOMER_SESSION_REFRESH_REUSED and revokes every credential in that session. Flint cannot tell the thief from the buyer, so it ends both and your app makes the buyer sign in again.

Revoke on sign-out, or all at once

One session when a buyer signs out of one device. Every session for a customer when they change a password, close the account, or you suspect a takeover.

Add a card from your own page

Creating a saved card under the session returns everything Stripe.js needs. Your page mounts Elements and confirms the setup; the browser talks to Stripe and never to Flint. The method turns active on a webhook.

Hand off into the hosted account

While Flint hosts the account, the same session call also returns a one-time account_url that signs the buyer straight in from your site, with no second login. Redirect to it at once; it lasts fifteen minutes by default.

Rate limited per buyer, not per merchant

Customer sessions carry their own rate limit, per session and per customer, separate from your merchant key. One buyer refreshing in a loop cannot spend your integration's budget.

refresh
curl -X POST https://api.withflintpay.com/v1/customer-sessions/refresh \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: refresh-cses-1kmn0aExample-004" \
  -d '{ "refresh_token": "flint_cref_example" }'

No API key. The idempotency key is required here, so a retried refresh cannot read as a replay.

sign the buyer out everywhere
curl -X POST \
  https://api.withflintpay.com/v1/customers/cus_1kmn0aExample/sessions/revoke \
  -H "Authorization: Bearer $FLINT_SECRET_KEY"

Every customer session for this buyer, in one call. Signing out of Flint's hosted account is separate, which matters if you run both during a migration.

POST /v1/me/payment-methods · 201 Created
{
  "data": {
    "payment_method": {
      "payment_method_id": "pm_1kmn0aExample",
      "status": "pending"
    },
    "client_setup": {
      "stripe": {
        "account_id": "acct_1kmn0aExample",
        "publishable_key": "pk_test_1kmn0aExample",
        "setup_intent": {
          "client_secret": "seti_1kmn0aExample_secret_example",
          "stripe_js_call": "confirm_setup"
        }
      }
    }
  }
}

Hand account_id, publishable_key, and the setup intent's client_secret to your page. The publishable key is Flint's and tracks the active payment mode, so use the one in the response.

the same session, while Flint hosts the account
{
  "data": {
    "customer_session_id": "cses_1kmn0aExample",
    "customer_id": "cus_1kmn0aExample",
    "account_url": "https://account.cedarandstone.com/auth?token=example",
    "account_url_expires_at": "2026-08-10T17:15:00Z",
    "secret": "flint_cses_example",
    "expires_at": "2026-08-10T18:00:00Z",
    "refresh_token": "flint_cref_example",
    "refresh_token_expires_at": "2026-08-17T17:00:00Z"
  }
}

account_url is the shortest-lived credential in the response for a reason. It belongs in a redirect, never in stored state or an email.

  • /v1/me/orders
  • /v1/me/payments
  • /v1/me/refunds
  • /v1/me/shipments
  • /v1/me/packages
  • /v1/me/invoices
  • /v1/me/subscriptions
  • /v1/me/payment-methods
  • /v1/me/addresses
  • /v1/me/returns
  • secret
  • refresh_token
  • account_url
  • expires_in_seconds
  • refresh_expires_in_seconds
  • account_url_expires_in_seconds
  • POST /v1/customer-sessions/refresh
  • POST /v1/customer-sessions/{customer_session_id}/revoke

Plus the receipt, invoice PDFs, credit notes, email changes, and account deletion. The SDKs cover every /v1/me route through their customer auth mode, which carries the session token instead of your merchant key. Build your own customer account → · Customer sessions → · SDKs →

05The email

Own the mail, or just the links.

Pointing every link Flint sends at your own pages is one setting, and it fixes last month's mail too, because links resolve when the buyer clicks. Sending the mail yourself is a second setting, one family at a time, built on events that keep firing either way.

just the links
curl -X PATCH https://api.withflintpay.com/v1/settings \
  -H "Authorization: Bearer $FLINT_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "customer_account": {
      "mode": "merchant_hosted",
      "merchant_account_url": "https://cedarandstone.com/account",
      "route_templates": {
        "order": "/orders/{resource_id}",
        "subscription": "/subscriptions/{resource_id}",
        "return": "/returns/{resource_id}"
      }
    }
  }'

Flint keeps sending. Every account link now lands in your app, at the route template for that resource, resolved when the buyer clicks.

or the whole family
curl -X PATCH https://api.withflintpay.com/v1/settings \
  -H "Authorization: Bearer $FLINT_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "customer_email_delivery": {
      "order_receipts": "merchant_sends",
      "fulfillment_updates": "merchant_sends",
      "subscription_lifecycle": "flint_sends",
      "dunning": "flint_sends",
      "returns": "flint_sends",
      "invoices": "flint_sends"
    }
  }'

Flint stops sending receipts and delivery notices. It keeps sending the other four, so the dunning that recovers your revenue still goes out.

FamilyWhat Flint sendsBuild your send from
order_receiptsPurchase receiptsorder.paid
fulfillment_updatesShipment and delivery noticesorder.fulfillment.shipment.updated
subscription_lifecycleRenewals, pauses, cancellations, trial endingssubscription.canceled
dunningFailed payment and past-due noticessubscription.payment_failed
returnsReturn approvals, labels, and resolutionsreturn.completed
invoicesInvoice delivery and remindersinvoice.paid

Switching a family to merchant_sends stops Flint's email for that family and nothing else. The events behind it keep firing, which is the entire mechanism: you subscribe to what Flint would have written about and write it yourself. Each family maps to more events than deserve a message, so you pick the transitions your buyers care about, and Flint's own version does not send one message per event either.

Nothing here is all-or-nothing. Most merchants who write their own receipts keep Flint sending dunning. branding decides how Flint's mail looks; a family on merchant_sends decides what it says. Customer email delivery → · Webhooks →

06Identity

The account handles identity, so you never do.

Sign-in, email changes, device sessions, and deletion requests are the parts of a buyer account nobody budgets for. They ship with it, on Flint's hosted account and through /v1/me for one you build.

No password to store

Flint emails the buyer a 6-digit code, and links in receipts sign them in on the way to the order they clicked. There is no password for you to store, reset, or leak.

Email changes confirm both addresses

A buyer's email is their identity, so the buyer changes it themselves: a code to the current address and a code to the new one, and nothing moves until both are confirmed.

Sign out other devices

The hosted account lists every device signed in and lets the buyer sign one out or all of them. Your build gets the same through session revocation.

Deletion goes through you

A buyer asks to delete their account and the request routes to you for a decision. Approval anonymizes the customer and keeps the orders, payments, and refunds your books need.

The three outcomes of a deletion request

  • customer.deletion_requested
  • customer.deletion_completed
  • customer.deletion_rejected

The completed event is your cue to delete any copy of the buyer's data you keep. Email preferences survive on their own, keyed rather than stored as an address, so the buyer's choices keep being honored after their identity is gone.

  • POST /v1/me/email-change-requests
  • POST /v1/me/deletion-requests
  • retention_policy
  • POST /v1/customers/{customer_id}/sessions/revoke

07Reviewed

Every claim above, pinned to the API.

Every merchant has a hosted buyer account from the first payment, with receipts linking to it.

customer_account.mode: flint_hosted

One settings call restyles the account, hosted checkout, and Flint's email together.

PATCH /v1/settings · branding

A custom hostname gets a certificate, its DNS records, and a status you read back.

presentation.custom_domain · customer_account_domain_status

A customer session is minted on your backend and scoped to one buyer.

POST /v1/customer-sessions

The /v1/me namespace has no customer_id, and sending one is rejected.

GET /v1/me/orders

Refresh rotates both credentials and needs no API key.

POST /v1/customer-sessions/refresh

A buyer can add a card from a page you built, without Flint in the browser.

POST /v1/me/payment-methods · client_setup

Repointing Flint's email links at your pages is a setting, resolved at click time.

customer_account.mode: merchant_hosted · route_templates

Buyer email can be taken over one family at a time; the events keep firing.

customer_email_delivery.*: merchant_sends

Account deletion routes to you and reports its outcome as three events.

POST /v1/me/deletion-requests

reviewed 2026-09-15 against the customer accounts guides and the published API · corrections: Flint Help

08Start

A working account from your first test payment.

Free sandbox keys, no credit card. The receipt for that payment already links to the account. Brand it, move it, or replace it whenever you get to it.

1 · take a test payment

Pay a sandbox payment link with 4242 4242 4242 4242. The receipt in your inbox links to the account, and the order is already in it.

2 · put your name on it

flint settings update --input branding.json
flint settings get

branding.json is the restyle request above. Reload the account and it is yours.

3 · or mint a session for your own UI

flint customer-sessions create --input session.json

session.json names the customer. The secret that comes back reads /v1/me for that buyer and nobody else.

Node

npm install @flintpay/node

CLI

npm install -g @flintpay/cli

Then read these

  1. 01
    Customer accounts guideBranding, the custom domain, the two modes, and deletion.
  2. 02
    Build your own customer accountThe /v1/me surface, adding a card, and pointing the email at your pages.
  3. 03
    Customer sessionsLifetimes, rotation, revocation, and the four failures.
  4. 04
    Customer email deliveryThe six families and the events behind each.
  5. 05
  6. 06

Skip the wait: npm install -g @flintpay/cli && flint signup creates your account and a sandbox key from the terminal. CLI guide

FAQ

Questions worth asking first.

Do I have to build a customer account?

No. Every Flint merchant has one at account.withflintpay.com from the first payment, and every account link in a receipt, shipping notice, dunning email, or return update already points at it. Buyers track orders, download invoices, manage subscriptions, update a saved card, and start a return there on day one. Branding it, moving it to your domain, and replacing it are three separate settings you can change later or never.

What can a buyer do without contacting me?

See every order with its parcels and tracking links, re-send a receipt, download an invoice PDF and pay an open invoice, add or remove a card and pick the default, pause, resume, cancel, or reactivate a subscription, change the card it bills or the address it ships to, skip a shipment, change how often it ships or how many among the options the store offers, swap to another flavor or size the store allows, order the next shipment now, keep an address book, start a return and follow it to the refund, change their email, sign out other devices, and ask for their account to be deleted. Each of those is also an endpoint under /v1/me, so an account you build can offer the same set.

How does a buyer sign in?

With a 6-digit code Flint emails to the address on the order, so there is no password for you to store or reset. Links in Flint's email carry the buyer straight to the order they clicked and sign them in on the way. If your own site already knows who the buyer is, a customer session returns a one-time account_url that signs them into the hosted account without a second login.

Can I put the customer account on my own domain?

Yes. A custom domain costs $15 a month per business and covers checkout and the customer account. Activate it in Billing, then set custom_domain in settings and Flint provisions the certificate and serves the account from account.yourbrand.com. Flint returns the DNS records to publish, checks them on a schedule, and reports the hostname as provisioning, active, or attention_required. The hostname is one you have already registered as a payment method domain, which is how Flint knows you control it. Nothing about the account changes except the address bar.

What happens to buyer links while my domain is being set up?

They keep working. Account links resolve when the buyer clicks, not when the email was sent, and they use Flint's own domain until your hostname is active. If an active hostname later stops resolving, links return to Flint's domain on their own until it recovers. A receipt from three weeks ago still opens the order either way.

How do I build my own customer account UI?

Authenticate the buyer with your own login, then have your backend mint a customer session for that one buyer. Its secret authorizes the /v1/me endpoints for orders, delivery, invoices, subscriptions, saved cards, addresses, and returns. There is no customer_id argument anywhere in that namespace, and sending one is rejected, so Flint keeps enforcing ownership on the server and a mistake in your front end cannot show one buyer another buyer's order. The SDKs cover every /v1/me route through their customer auth mode.

Can I send the buyer emails myself instead of Flint?

Yes, one family at a time. Receipts, delivery updates, subscription lifecycle, dunning, returns, and invoices each have their own setting. Switching a family to merchant_sends stops Flint's email for that family and nothing else, because the webhook events behind it keep firing, and that is what you build your send from. Most merchants who write their own receipts keep Flint sending dunning. If you only want Flint's mail to link to your own pages, set customer_account.mode instead and leave delivery alone.