Building a production‑grade Stripe Identity plugin for WordPress/WooCommerce
If you’re interested in secure, real‑world integrations like this one, explore my WordPress & API portfolio or Custom Web Solution services, and feel free to get in touch.
Overview
I rebuilt a custom WordPress/WooCommerce integration for
Stripe Identity
to make it secure, maintainable, and admin‑friendly. The project involved migrating to Stripe’s modern
StripeClient
, retrieving sensitive verified data with restricted keys, enforcing
Verification Flows, providing an on‑demand refresh from Stripe, and securely
downloading identity images. The result is a production‑grade plugin that reduces support time,
increases data reliability, and delivers a better admin experience.
Goals
- Migrate from deprecated static Stripe calls to
StripeClient
. - Access sensitive verified outputs (DOB, ID number, document number/expiration) using restricted keys and targeted
expand
parameters. - Enforce Verification Flows with admin‑configured Flow IDs (separate Test/Live).
- Add an admin “Refresh verification data” action to re‑sync user records from Stripe on demand.
- Download and store identity images (front, back, selfie) with short‑lived FileLinks; show clickable previews in the user profile.
- Improve reliability, logging, and error handling.
Approach
I re‑architected the plugin into three layers:
- API Integration — All calls use
\Stripe\StripeClient
, with per‑request key selection.
The restricted key (rk_…) is preferred for sensitive reads and Identity files; the secret key is used as a fallback where appropriate.
Reference: Stripe PHP SDK. - Persistence — Verified values are normalized and stored to user meta (e.g.,
birthdate
as dd‑mm‑yyyy).
Larger Stripe objects are serialized for troubleshooting via WordPress user meta APIs
(update_user_meta). - Admin UX — In the WordPress user profile, admins get status, structured fields, and a one‑click “Refresh from Stripe,” plus clickable verification image previews.
AJAX is implemented the WordPress way (WP AJAX guide).
Implementation highlights
1) Modern StripeClient everywhere
I replaced static API calls with the instance‑based \Stripe\StripeClient
, which is the supported, clearer approach.
Each operation initializes the client with the correct key (restricted for sensitive/identity files; secret otherwise). This migration
eliminated edge cases caused by older patterns and aligned the codebase with current Stripe guidance.
2) Sensitive outputs via restricted keys + expand
Sensitive PII requires restricted keys to access (see Stripe’s restricted keys). I added targeted expand paths when retrieving the verification session to surface nested fields:
verified_outputs
,verified_outputs.dob
,verified_outputs.id_number
last_verification_report
,last_verification_report.id_number
last_verification_report.document.number
,last_verification_report.document.expiration_date
last_verification_report.selfie
API references: Verification Sessions, Verification Reports.
I normalized:
- DOB →
dd‑mm‑yyyy
(handling object/array/string variants). - Document expiration →
dd/mm/yyyy
when full date exists; otherwisemm/yyyy
. - ID / Document numbers → stored under clear meta keys for the admin UI.
3) Strict Verification Flows
The plugin includes settings for Test and Live Flow IDs, and session creation requires a valid Flow (Verification Flows). No fallback to ad‑hoc sessions. If misconfigured, the admin sees a clear error. This enforces a consistent, policy‑compliant verification path and makes your onboarding behavior predictable.
Related work I do in this area: Stripe Integration Services and WordPress Development.
4) Admin “Refresh verification data”
In the user profile, an AJAX‑powered button re‑fetches the user’s verification session with all required expands, retrieves the report,
and updates meta. It’s secured with nonces and capability checks. This is extremely helpful for support and reconciliation—no waiting
for background jobs. On the wire: verificationSessions.retrieve
(with expands) and conditionally
verificationReports.retrieve
.
If you need this pattern implemented in your own stack, drop me a note via my contact page.
5) Secure identity image handling
Identity images are sensitive. I followed Stripe’s recommendations:
short‑lived FileLinks via the restricted key,
immediate server‑side download using the WordPress HTTP API, and local storage at
wp-content/uploads/stripe-identity/{user_id}/
. Only the local path, safe URL, and minimal metadata are kept for admin preview.
Stripe’s structures vary by document type:
- Some return
document.files[0]
(front) anddocument.files[1]
(back). - Others provide
document.front
/document.back
. - Occasionally, the selfie block includes a
document
file reference.
I consolidated the logic to detect and download front/back across these variants. In the admin profile, thumbnails for front/back/selfie are clickable and open full images in a new tab.
6) Better errors and observability
I added compact logging around key paths:
- Which key is in use (restricted vs secret, with safe prefix for verification).
- Which document properties/file IDs were detected.
- Any download failure (HTTP status, file missing, blank image).
This turned “why is the back image missing?” into a clear outcome: either the document is single‑sided (expected) or a specific download step failed (now visible in logs).
Admin experience
- Status: prominent “Verified” state when Stripe confirms the identity.
- Personal info: first/last name and DOB, normalized for clarity.
- Address: verified fields presented as a compact, readable block.
- Document: type/status, document number, expiration date.
- Images: front/back/selfie thumbnails, each clickable to open full‑size in a new tab.
- Refresh: one‑click re‑sync from Stripe with nonce and capability checks.
Impact
- Higher reliability of sensitive PII via restricted keys and targeted expands.
- Faster support with the admin refresh and clearer data presentation.
- Future‑proofed codebase using StripeClient and better patterns.
- Security‑aware image handling and minimal data storage.
What I’d build next
- Audit trail: lightweight per‑user verification event log (admin viewable).
- Retention controls: configurable image cleanup policies by age or state.
- Granular roles: shop managers can refresh; only admins change settings.
- Bulk utilities: batch re‑sync for cohorts or periodic data hygiene.
Files & APIs
public/class-stripe-identity-public.php
admin/class-stripe-identity-admin.php
admin/js/stripe-identity-admin.js
Stripe: identity.verificationSessions.retrieve, identity.verificationReports.retrieve, fileLinks.create, stripe-php.
FAQ's
Why use a restricted key with Stripe Identity?
Restricted keys are required to access sensitive files and verified PII safely and to minimize exposure. See
Stripe’s restricted keys.
What are Verification Flows?
Flows define a consistent, policy‑compliant capture process. The plugin enforces Flow IDs per environment to ensure every session follows the same steps.
Read more: Stripe Identity Flows.
How are identity images handled securely?
Short‑lived FileLinks created with a restricted key; server‑side download; only local paths and minimal metadata are stored for admin use.
FileLinks docs: Stripe FileLinks.
Can I re‑sync data after a user completes verification?
Yes. The admin “Refresh verification data” button retrieves the latest session/report and updates user meta on demand.
Does this work in test and live modes?
Yes. The plugin has separate settings for Test/Live API keys, restricted keys, and Flow IDs.
For similar engagements, see my portfolio or contact me to scope your integration.