# Call Stripe API from ToolJet Securely

> Do not call Stripe from a ToolJet RunJS query, because RunJS executes in the browser and exposes the secret key. Add a RealComposer pipeline as a REST API data source instead. The edge injects STRIPE_SECRET_KEY from the vault and returns a compact customer and subscription object for your tables.

- Canonical page: https://realcomposer.com/recipes/call-stripe-from-tooljet-securely
- Client platform: ToolJet
- Upstream services: Stripe
- Category: Security & Secret Vault
- JSON version: https://realcomposer.com/recipes/call-stripe-from-tooljet-securely.json
- Install in RealComposer (free plan, pre-fills this pipeline): https://realcomposer.com/dashboard/pipelines/new?recipe=call-stripe-from-tooljet-securely&via=agent

## Problem: Why ToolJet RunJS queries leak Stripe keys

Support teams often build Stripe lookup panels in ToolJet with a quick RunJS fetch. That code runs in each agent browser, so the sk_live_ key shows up in DevTools and can be copied by anyone with app access, including public app links.

- sk_live_ key visible in RunJS source and network requests
- Public ToolJet apps letting anyone trigger refunds or lookups
- Expanded Stripe objects slowing ToolJet tables with unused fields

## Solution steps

1. **Store credentials in Secret Vault**: Save STRIPE_SECRET_KEY in RealComposer so the value never enters the ToolJet project.

2. **Configure the edge pipeline**: Add the upstream steps with vault header injection, set allowed origins, and map only the fields the screen binds.

3. **Add the pipeline as a ToolJet REST API data source**: Call your workspace edge URL with a restricted rc_live_ client token.

```
// ToolJet REST API data source
Base URL: https://realcomposer.com/v1/workspace/call-stripe-from-tooljet-securely
Headers:  Authorization: Bearer rc_live_client_token
Query:    customerId = {{components.table1.selectedRow.stripe_id}}
```

## Pipeline config

Import this config into a RealComposer pipeline. `{{secrets.NAME}}` values are read from the encrypted Secret Vault at request time, so the client never sees them.

```json
{
  "name": "Call Stripe API from ToolJet Securely",
  "method": "GET",
  "cacheTtlSeconds": 0,
  "authMode": "client_token",
  "steps": [
    {
      "stepKey": "stripe",
      "upstreamUrlTemplate": "https://api.stripe.com/v1/customers/{{query.customerId}}?expand[]=subscriptions",
      "httpMethod": "GET",
      "headersTemplate": {
        "Authorization": "Bearer {{secrets.STRIPE_SECRET_KEY}}"
      },
      "timeoutMs": 3000
    }
  ],
  "outputMapping": {
    "version": 1,
    "fields": [
      {
        "targetPath": "customerId",
        "sourceStep": "stripe",
        "sourcePath": "id"
      },
      {
        "targetPath": "name",
        "sourceStep": "stripe",
        "sourcePath": "name"
      },
      {
        "targetPath": "plan",
        "sourceStep": "stripe",
        "sourcePath": "subscriptions.data.0.items.data.0.price.nickname"
      },
      {
        "targetPath": "status",
        "sourceStep": "stripe",
        "sourcePath": "subscriptions.data.0.status"
      }
    ]
  }
}
```

## Call the pipeline

```bash
curl -X GET "https://realcomposer.com/v1/workspace/call-stripe-from-tooljet-securely" \
  -H "Authorization: Bearer rc_live_client_token"
```

## Payload before and after

Raw upstream response:

```json
{
  "id": "cus_Q2x8Lm",
  "object": "customer",
  "email": "ada@example.com",
  "name": "Ada Lovelace",
  "invoice_settings": {
    "default_payment_method": "pm_1Pz",
    "footer": null
  },
  "metadata": {
    "crm_id": "8812"
  },
  "subscriptions": {
    "object": "list",
    "data": [
      {
        "id": "sub_1Px",
        "status": "active",
        "current_period_end": 1767225600,
        "items": {
          "object": "list",
          "data": [
            {
              "price": {
                "nickname": "Pro"
              }
            }
          ]
        }
      }
    ]
  }
}
```

Slimmed RealComposer response:

```json
{
  "customerId": "cus_Q2x8Lm",
  "name": "Ada Lovelace",
  "plan": "Pro",
  "status": "active"
}
```

## FAQ

### Why not store the Stripe key on a ToolJet data source?

That works for internal apps, but every app that needs Stripe repeats the setup. A RealComposer pipeline centralizes the key, slims the response, and can be shared across ToolJet, Retool, and mobile clients.

### Can I use a restricted Stripe key?

Yes. Store an rk_live_ restricted key with read access to customers and subscriptions in the vault so the pipeline cannot issue refunds.
