Bonaventure OgetoBy Bonaventure Ogeto|

Paystack Metadata: Passing Custom Data Through a Transaction

Pass a metadata object when initializing a Paystack transaction. It accepts any JSON key-value pairs. Paystack stores this data and returns it in verify responses and webhook payloads at data.metadata. Use it to link transactions to your internal records (order IDs, user IDs, product names) without needing a separate mapping table.

What Metadata Is For

When a Paystack webhook arrives at your server with a charge.success event, you need to know what to do with the payment. Which order does it belong to? Which user paid? What product should be delivered? You can answer these questions two ways:

  1. Look up the transaction reference in your database: This works if you stored a mapping between the reference and the order before initializing the transaction.
  2. Read the metadata from the webhook payload: This works if you attached the relevant IDs to the transaction when you initialized it.

Both approaches work. Metadata makes the second approach possible and can serve as a useful backup or supplement to reference-based lookups.

// Initialize with metadata
var response = await fetch('https://api.paystack.co/transaction/initialize', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    email: 'customer@example.com',
    amount: 350000,
    currency: 'NGN',
    reference: 'order_2026_00142',
    metadata: {
      order_id: 'ORD-2026-00142',
      user_id: 'usr_8f3k2n',
      product_type: 'subscription',
      plan: 'annual_pro',
    },
  }),
});

After payment, when you verify the transaction or receive a webhook, the metadata comes back in data.metadata:

// In your webhook handler
var metadata = event.data.metadata;
console.log(metadata.order_id);      // 'ORD-2026-00142'
console.log(metadata.user_id);       // 'usr_8f3k2n'
console.log(metadata.product_type);  // 'subscription'
console.log(metadata.plan);          // 'annual_pro'

Metadata Structure

The metadata field is a JSON object. You can nest objects and arrays within it, though flat structures are easier to work with.

// Flat structure (recommended)
metadata: {
  order_id: 'ORD-2026-00142',
  user_id: 'usr_8f3k2n',
  items_count: 3,
  source: 'web_checkout',
}

// Nested structure (works but harder to query)
metadata: {
  order: {
    id: 'ORD-2026-00142',
    items_count: 3,
  },
  user: {
    id: 'usr_8f3k2n',
    tier: 'premium',
  },
}

Flat structures are easier to read in the Paystack dashboard and simpler to extract in webhook handlers. Use flat keys unless nesting truly adds clarity.

Data types

Metadata values can be strings, numbers, booleans, arrays, or nested objects. However, when metadata comes back from Paystack (in verify responses or webhooks), all values are serialized as JSON. Be prepared for numbers to arrive as strings in some cases and always validate types in your handler.

// Be defensive when reading metadata values
var orderId = String(metadata.order_id);
var itemsCount = Number(metadata.items_count);

Custom Fields for Display

The custom_fields key is a special metadata structure. It is an array of objects, each with display_name, variable_name, and value. Paystack renders these on the checkout page and in the transaction details on your dashboard.

metadata: {
  order_id: 'ORD-2026-00142',
  user_id: 'usr_8f3k2n',
  custom_fields: [
    {
      display_name: 'Order Number',
      variable_name: 'order_number',
      value: 'ORD-2026-00142',
    },
    {
      display_name: 'Customer Name',
      variable_name: 'customer_name',
      value: 'Amina Okafor',
    },
    {
      display_name: 'Delivery Address',
      variable_name: 'delivery_address',
      value: '15 Adeola Odeku, VI, Lagos',
    },
  ],
}

Custom fields serve two audiences:

  • The customer: They see the custom fields on the checkout page below the payment form, which serves as an order confirmation before they pay.
  • Your support team: When they look up a transaction on the Paystack dashboard, custom fields appear prominently, making it easy to identify what the transaction was for without querying your database.

You can have both regular metadata keys (for your backend logic) and custom_fields (for display) in the same metadata object. They do not interfere with each other. For advanced custom field patterns, see collecting extra checkout information.

The cancel_action URL

Paystack recognizes a special metadata key called cancel_action. If you set it to a URL, Paystack displays a "Cancel" button on the checkout page that redirects the customer to that URL when clicked. Without it, the cancel button closes the popup or navigates back.

metadata: {
  cancel_action: 'https://yoursite.com/cart',
  order_id: 'ORD-2026-00142',
}

This is useful when you want to redirect the customer to a specific page (like their cart or the product page) instead of just closing the checkout. It only works with redirect checkout; for inline/popup checkout, the onCancel callback handles the cancellation.

Common Metadata Use Cases

E-commerce order tracking

metadata: {
  order_id: 'ORD-2026-00142',
  cart_id: 'cart_9x8k2',
  items_count: 3,
  shipping_method: 'express',
  coupon_code: 'SAVE20',
}

SaaS subscription activation

metadata: {
  user_id: 'usr_8f3k2n',
  plan: 'pro_annual',
  workspace_id: 'ws_4m2n',
  trial_conversion: true,
}

Event ticket purchase

metadata: {
  event_id: 'evt_devfest_2026',
  ticket_type: 'vip',
  quantity: 2,
  attendee_email: 'customer@example.com',
  custom_fields: [
    {
      display_name: 'Event',
      variable_name: 'event_name',
      value: 'DevFest Lagos 2026',
    },
    {
      display_name: 'Ticket Type',
      variable_name: 'ticket_type',
      value: 'VIP (x2)',
    },
  ],
}

Marketplace or multi-vendor payment

metadata: {
  vendor_id: 'vendor_abc',
  platform_fee_percent: 10,
  buyer_id: 'buyer_xyz',
  listing_id: 'listing_789',
}

In each case, the metadata travels with the transaction. When the webhook arrives, you read the metadata to determine what to do: create tickets, activate a subscription, update an order status, or split funds between vendors.

Limits and Restrictions

Paystack does not publish an exact size limit for metadata, but the practical limit is a few kilobytes. Here are the guidelines:

  • Keep it small: Store IDs and short strings, not entire objects or large text blobs. If you need to pass a full order manifest, store it in your database and pass only the order ID in metadata.
  • No binary data: Do not base64-encode files, images, or PDFs into metadata. The request will likely fail or be rejected.
  • No sensitive data: Metadata is visible on the Paystack dashboard and in API responses. Do not store passwords, tokens, secret keys, or sensitive PII like national ID numbers.
  • String encoding: Keep values as simple strings and numbers. Special characters, emoji, and non-ASCII text generally work but add to the payload size.

A reasonable upper bound: keep the serialized metadata JSON under 5KB. Most practical use cases are well under 1KB.

// BAD: too much data in metadata
metadata: {
  full_cart: [
    { name: 'Product 1', price: 5000, description: '500 words of text...' },
    { name: 'Product 2', price: 3000, description: '500 words of text...' },
    // ... 20 more items
  ],
}

// GOOD: just the IDs, look up the rest from your database
metadata: {
  cart_id: 'cart_9x8k2',
  items_count: 22,
}

Reading Metadata in Webhooks

The webhook payload includes your metadata at data.metadata. Here is how to use it in your handler:

app.post('/webhooks/paystack', function(req, res) {
  // Return 200 immediately
  res.sendStatus(200);

  var event = req.body;

  if (event.event === 'charge.success') {
    var metadata = event.data.metadata || {};

    // Use metadata to determine what to do
    if (metadata.product_type === 'subscription') {
      activateSubscription(metadata.user_id, metadata.plan);
    } else if (metadata.product_type === 'order') {
      fulfillOrder(metadata.order_id);
    } else {
      // Fallback: look up by reference
      processGenericPayment(event.data.reference);
    }
  }
});

Always have a fallback. If metadata is missing or malformed (which can happen in edge cases), fall back to looking up the transaction by its reference. Your system should never break because metadata is empty.

For the full webhook handling guide, see the complete webhook engineering guide.

Stay Up to Date

We update these Paystack integration guides when API changes affect how metadata works or when Paystack adds new metadata features.

Sign up for the McTaba newsletter to get notified about Paystack changes that affect your integration.

Key Takeaways

  • The metadata field accepts any JSON object. Whatever you put in comes back in verify responses and webhook payloads at data.metadata.
  • Use metadata to store order IDs, user IDs, plan names, and anything else your fulfillment logic needs to identify what the payment was for.
  • custom_fields is a special metadata structure that Paystack displays on the checkout page and dashboard. Use it for human-readable transaction labels.
  • Metadata has size limits. Keep it under a few kilobytes. Do not store large blobs, base64 images, or entire cart inventories.
  • Metadata is not encrypted. Do not store sensitive information like passwords, API keys, or personally identifiable information that should not appear in logs.
  • You can combine regular metadata keys and custom_fields in the same metadata object. Regular keys are for your backend; custom_fields are for display.

Frequently Asked Questions

Can I update metadata after a transaction is initialized?
No. Metadata is set at initialization time and cannot be modified after. If you need to change the metadata, initialize a new transaction. To track changing data, store it in your database and link it via the transaction reference.
Is metadata included in the transaction export from the Paystack dashboard?
Yes. When you export transactions from the Paystack dashboard, metadata fields (including custom_fields) are included in the export. This makes metadata useful for reporting and reconciliation.
Can metadata keys conflict with Paystack internal fields?
Paystack reserves a few metadata keys like custom_fields and cancel_action. Other than those, you can use any key names. To be safe, prefix your keys with your app name or use a namespace like app_order_id instead of just id.
Does metadata affect transaction processing or fees?
No. Metadata is purely informational. It does not affect how Paystack processes the transaction, what fees apply, or how settlement works. It is pass-through data for your use.
Can I search transactions by metadata values in the Paystack dashboard?
The Paystack dashboard has limited search by metadata. You can search by reference, email, and amount. For metadata-based queries, use the Paystack API to list transactions and filter by metadata values on your end, or maintain a searchable index in your own database.

Ready to build real-world apps?

Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.

Apply to the McTaba Marathon