Stripe metadata lets you attach up to 50 key-value pairs to any object — charges, customers, subscriptions, invoices, and more. Use metadata to store internal references like order IDs, user IDs, and campaign tags. Metadata is searchable in the Dashboard and accessible via the API, making it essential for reconciliation, analytics, and debugging.
Using Metadata to Track Custom Data on Stripe Objects
Stripe metadata is a powerful feature that lets you attach custom key-value pairs to charges, PaymentIntents, customers, subscriptions, and other objects. This bridges the gap between Stripe data and your internal systems. Store your order ID, user ID, campaign source, or any reference you need for reconciliation, reporting, and support. Metadata is visible in the Dashboard and returned in API responses and webhooks.
Prerequisites
- A Stripe account in test mode
- Node.js 18+ with the Stripe npm package
- An understanding of your internal reference system (order IDs, user IDs, etc.)
Step-by-step guide
Add metadata when creating a PaymentIntent
Add metadata when creating a PaymentIntent
Pass a metadata object with key-value pairs when creating a PaymentIntent. Keys and values must be strings. You can store up to 50 keys, with key names up to 40 characters and values up to 500 characters.
1const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);23const paymentIntent = await stripe.paymentIntents.create({4 amount: 4999, // $49.995 currency: 'usd',6 metadata: {7 order_id: 'ORD-2026-1234',8 user_id: 'usr_abc123',9 product: 'Pro Plan Annual',10 campaign: 'spring_sale_2026',11 },12 automatic_payment_methods: { enabled: true },13});Expected result: The PaymentIntent is created with custom metadata attached. This metadata appears in the Dashboard and webhook events.
Add metadata to customers
Add metadata to customers
Attach metadata to customer objects to store user IDs, account types, signup sources, or any customer-level tracking data.
1const customer = await stripe.customers.create({2 email: 'user@example.com',3 name: 'Jane Smith',4 metadata: {5 internal_user_id: 'usr_abc123',6 plan_tier: 'pro',7 signup_source: 'referral',8 company: 'Acme Corp',9 },10});Expected result: The customer object has metadata accessible from the Dashboard, API, and webhook events.
Update metadata on existing objects
Update metadata on existing objects
You can update metadata at any time. Setting a key to an empty string removes it. Other existing keys are preserved.
1// Add or update metadata keys2await stripe.paymentIntents.update('pi_abc123', {3 metadata: {4 fulfillment_status: 'shipped',5 tracking_number: 'UPS-1Z999AA10123456784',6 },7});89// Remove a specific key by setting it to empty string10await stripe.paymentIntents.update('pi_abc123', {11 metadata: {12 campaign: '', // This removes the 'campaign' key13 },14});Expected result: Metadata is updated on the existing PaymentIntent. New keys are added, and empty-string values remove keys.
Search by metadata in the Dashboard
Search by metadata in the Dashboard
In the Stripe Dashboard, go to Payments and use the search bar. You can search by metadata values. For example, searching for an order ID will find the payment with that metadata. This is invaluable for customer support.
Expected result: You can find specific payments by searching for metadata values in the Dashboard search bar.
Read metadata in webhook events
Read metadata in webhook events
Metadata is included in webhook event payloads. This lets you correlate Stripe events back to your internal systems without making additional API calls. Teams at RapidDev use this to automate order fulfillment and CRM updates.
1// In your webhook handler2if (event.type === 'payment_intent.succeeded') {3 const paymentIntent = event.data.object;4 const orderId = paymentIntent.metadata.order_id;5 const userId = paymentIntent.metadata.user_id;67 console.log(`Payment succeeded for order ${orderId}, user ${userId}`);8 // Trigger order fulfillment, update your database, etc.9}Expected result: Your webhook handler reads metadata from the event to identify the associated order and user.
Complete working example
1require('dotenv').config();2const express = require('express');3const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);45const app = express();67// Create payment with metadata8app.post('/api/create-payment',9 express.json(),10 async (req, res) => {11 const { amount, currency, orderId, userId, productName } = req.body;1213 try {14 const paymentIntent = await stripe.paymentIntents.create({15 amount,16 currency: currency || 'usd',17 metadata: {18 order_id: orderId,19 user_id: userId,20 product: productName,21 created_at: new Date().toISOString(),22 },23 automatic_payment_methods: { enabled: true },24 });2526 res.json({ clientSecret: paymentIntent.client_secret });27 } catch (err) {28 res.status(500).json({ error: err.message });29 }30 }31);3233// Webhook reads metadata34app.post('/webhook',35 express.raw({ type: 'application/json' }),36 (req, res) => {37 const sig = req.headers['stripe-signature'];38 let event;3940 try {41 event = stripe.webhooks.constructEvent(42 req.body, sig, process.env.STRIPE_WEBHOOK_SECRET43 );44 } catch (err) {45 return res.status(400).send(`Webhook Error: ${err.message}`);46 }4748 if (event.type === 'payment_intent.succeeded') {49 const pi = event.data.object;50 console.log('Payment succeeded:', {51 stripe_id: pi.id,52 order_id: pi.metadata.order_id,53 user_id: pi.metadata.user_id,54 product: pi.metadata.product,55 amount: pi.amount / 100,56 currency: pi.currency,57 });58 // TODO: fulfill order, update database59 }6061 res.json({ received: true });62 }63);6465const PORT = process.env.PORT || 3000;66app.listen(PORT, () => console.log(`Server on port ${PORT}`));Common mistakes when storing metadata on Stripe charges
Why it's a problem: Storing sensitive data like passwords or full credit card numbers in metadata
How to avoid: Never store sensitive data in metadata. It's visible in the Dashboard and API responses. Store only non-sensitive references like IDs and labels.
Why it's a problem: Using non-string values in metadata
How to avoid: All metadata values must be strings. Convert numbers and booleans to strings: metadata: { count: '5', active: 'true' }.
Why it's a problem: Exceeding the 50-key limit
How to avoid: Stripe allows a maximum of 50 metadata keys per object. Store high-cardinality data in your own database and use a single reference ID in metadata.
Why it's a problem: Using inconsistent key names across your codebase
How to avoid: Standardize your metadata keys (e.g., always use 'order_id' not sometimes 'orderId' or 'order-id'). Document your metadata schema.
Best practices
- Always include your internal order/user ID in payment metadata for reconciliation
- Use consistent, snake_case key names across all Stripe objects
- Document your metadata schema so your team uses the same keys
- Never store sensitive data (PII, passwords, full card numbers) in metadata
- Use metadata in webhooks to correlate Stripe events to your internal records
- Keep values under 500 characters — use IDs and references, not full data
- Search by metadata in the Dashboard to quickly find payments for support
- Include a timestamp in metadata for debugging timing issues
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
Write a Node.js Express server that creates Stripe PaymentIntents with metadata (order_id, user_id, product name, timestamp). Include a webhook handler that reads the metadata from payment_intent.succeeded events for order fulfillment. Use raw body parsing for the webhook route.
Build a Stripe payment system in Node.js that attaches metadata to PaymentIntents for order tracking. Include creation with metadata, webhook reading of metadata, and proper raw body parsing for signature verification.
Frequently asked questions
What is Stripe metadata?
Metadata is a set of up to 50 key-value pairs you can attach to most Stripe objects. Keys can be up to 40 characters and values up to 500 characters. All values must be strings.
Can I search by metadata in the Stripe Dashboard?
Yes. Use the search bar in the Payments, Customers, or Subscriptions sections. You can search by metadata values to find specific objects.
Is metadata included in webhook events?
Yes. Metadata is included in the event.data.object payload for all webhook events, allowing you to correlate Stripe events to your internal systems.
Can I update metadata after creating an object?
Yes. Use the update method (e.g., stripe.paymentIntents.update) to add, change, or remove metadata keys. Setting a key's value to an empty string removes it.
Is there a cost for using metadata?
No. Metadata is a free feature available on all Stripe objects and plans. There is no additional charge for storing or querying metadata.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation