Skip to main content
RapidDev - Software Development Agency
stripe-guide

How to store metadata on Stripe charges

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.

What you'll learn

  • How to add metadata to Stripe charges and PaymentIntents
  • How to use metadata for order tracking and reconciliation
  • How to search metadata in the Stripe Dashboard
  • Best practices for structuring metadata keys and values
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner5 min read10 minutesStripe API v2024-12+, Node.js 18+March 2026RapidDev Engineering Team
TL;DR

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

1

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.

typescript
1const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
2
3const paymentIntent = await stripe.paymentIntents.create({
4 amount: 4999, // $49.99
5 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.

2

Add metadata to customers

Attach metadata to customer objects to store user IDs, account types, signup sources, or any customer-level tracking data.

typescript
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.

3

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.

typescript
1// Add or update metadata keys
2await stripe.paymentIntents.update('pi_abc123', {
3 metadata: {
4 fulfillment_status: 'shipped',
5 tracking_number: 'UPS-1Z999AA10123456784',
6 },
7});
8
9// Remove a specific key by setting it to empty string
10await stripe.paymentIntents.update('pi_abc123', {
11 metadata: {
12 campaign: '', // This removes the 'campaign' key
13 },
14});

Expected result: Metadata is updated on the existing PaymentIntent. New keys are added, and empty-string values remove keys.

4

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.

5

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.

typescript
1// In your webhook handler
2if (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;
6
7 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

metadata-example.js
1require('dotenv').config();
2const express = require('express');
3const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
4
5const app = express();
6
7// Create payment with metadata
8app.post('/api/create-payment',
9 express.json(),
10 async (req, res) => {
11 const { amount, currency, orderId, userId, productName } = req.body;
12
13 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 });
25
26 res.json({ clientSecret: paymentIntent.client_secret });
27 } catch (err) {
28 res.status(500).json({ error: err.message });
29 }
30 }
31);
32
33// Webhook reads metadata
34app.post('/webhook',
35 express.raw({ type: 'application/json' }),
36 (req, res) => {
37 const sig = req.headers['stripe-signature'];
38 let event;
39
40 try {
41 event = stripe.webhooks.constructEvent(
42 req.body, sig, process.env.STRIPE_WEBHOOK_SECRET
43 );
44 } catch (err) {
45 return res.status(400).send(`Webhook Error: ${err.message}`);
46 }
47
48 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 database
59 }
60
61 res.json({ received: true });
62 }
63);
64
65const 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.

ChatGPT Prompt

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.

Stripe Prompt

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.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.