Flow overview#

  1. Customer enters email on the login form.
  2. WordPress verifies the nonce and applies the rate limiter (5/10min per email + per IP).
  3. WordPress optionally looks up the customer in Stripe by email.
  4. WordPress generates a 20-character token and stores its SHA-256 hash in a transient with a one-hour TTL.
  5. WordPress sends a magic link via wp_mail() using the configured Customer Portal slug.
  6. Customer opens the link in their email.
  7. WordPress re-hashes the URL token, looks up + deletes the transient (one-time use).
  8. Stripe finds or creates the customer.
  9. Stripe opens a Billing Portal session and redirects.
  10. Customer manages billing on Stripe's hosted portal.
  11. Stripe returns the customer to your configured Return URL.

Step 1 — Customer submits email#

The customer enters their email on:

WordPress verifies the form nonce (lscp_stripe_portal_login_action).

Step 2 — Rate-limit + optional existing-customer check#

Before any email is sent, the request is checked against the rate limiter (5 requests / 10 minutes, per email and per IP). If either bucket overflows the customer sees “Too many requests. Please wait a few minutes and try again.” See Rate limiter.

If Only allow existing Stripe customers to login is enabled in settings:

  • The plugin queries Stripe for a customer with that email.
  • If none exists, no email is sent.
  • The customer sees the same enumeration-safe message either way.

If the setting is unchecked (default), the plugin proceeds without the Stripe lookup — any valid email gets a link, and a new Stripe Customer is auto-created when the link is redeemed.

Step 3 — Magic link email#

When allowed to proceed:

  1. A random token is generated (wp_generate_password, 20 characters).
  2. The plugin stores the SHA-256 hash of the token in a WordPress transient keyed by lscp_stripe_login_token_{sha256(token)}, mapped to the customer’s email, with 3600 seconds (1 hour) TTL. The raw token is never written to disk — a database snapshot does not expose unredeemed login links.
  3. An HTML email is sent via wp_mail() with subject Login to Stripe Customer Portal.

Example magic-link email sent to the customer

The link format (using your configured Customer Portal Slug, not a hardcoded path):

https://yoursite.com/{your-slug}/?token=XXXXXXXX

The customer sees a mode-aware confirmation message on the form:

  • Default mode: “A login link is on its way. Please check your inbox for the link to access your Stripe Customer Portal.”
  • Existing-only mode: “If your email address is associated with a Stripe customer, a login link is on its way. Please check your inbox.”

Step 4 — Customer clicks the link#

On GET with a valid token query parameter:

  1. WordPress computes SHA-256(token) and looks up the matching transient to load the email.
  2. The transient is deleted immediately (one-time use, even if the rest of the flow fails).
  3. If the token hash has no matching transient (expired, already used, or never issued), the customer sees: Invalid or expired token.

Step 5 — Stripe Customer Portal session#

The plugin:

  1. Sets the Stripe API key from settings.
  2. Finds a Stripe Customer by email, or creates one if none exists (unless blocked by existing-customer-only mode earlier in the flow).
  3. Creates a BillingPortal\Session with the configured return URL.
  4. Redirects the browser to Stripe's hosted portal URL.

Stripe-hosted Customer Portal after successful login

Step 6 — Return to your site#

When the customer exits the portal, Stripe redirects to your Redirect URL (see Settings Reference). If unset, the default is your customer portal login page.

Security notes#

  • No WordPress accounts are created for customers.
  • Enumeration-safe messaging — The post-submit message is mode-aware and does not reveal whether an email exists in Stripe.
  • Hashed tokens at rest — Stored as SHA-256(token); a database snapshot can’t replay outstanding links.
  • One-time tokens — Each link works once and expires after one hour.
  • Rate limiter — 5 requests / 10 minutes per email and per IP, on every submission path (endpoint and shortcode).
  • Nonces protect form POST requests from CSRF.

See Security and Privacy for the full model.

Troubleshooting