{
  "slug": "call-stripe-from-flutter-securely",
  "title": "Call Stripe API from Flutter Securely",
  "metaDescription": "Call Stripe from a Flutter app without shipping sk_live_ in the binary. RealComposer injects the secret at the edge and returns compact subscription JSON.",
  "headline": "Flutter subscription screens without Stripe secrets on device",
  "category": "security",
  "categoryLabel": "Security & Secret Vault",
  "urgency": 10,
  "clientPlatform": "Flutter",
  "upstreamServices": [
    "Stripe"
  ],
  "searchIntentQuery": "call stripe api from flutter without secret key",
  "directAnswerSummary": "A Stripe secret key passed with dart-define or kept in Dart source is compiled into the APK or IPA and can be extracted. Ship only a RealComposer client token in the Flutter app. The edge pipeline injects STRIPE_SECRET_KEY and returns plan and status fields for your billing screen.",
  "problem": {
    "title": "Why Flutter apps cannot hold Stripe secret keys",
    "explanation": "Flutter release builds keep string constants and dart-define values readable, even with obfuscation enabled. A leaked secret key allows full account access, so Stripe server calls must come from a trusted backend or edge.",
    "risks": [
      "sk_live_ recovered from APK or IPA string tables",
      "Full Stripe customer objects parsed on the UI isolate",
      "Key rotation forcing a new store release"
    ]
  },
  "solutionSteps": [
    {
      "stepNumber": 1,
      "title": "Store credentials in Secret Vault",
      "description": "Save STRIPE_SECRET_KEY in RealComposer so the value never enters the Flutter project."
    },
    {
      "stepNumber": 2,
      "title": "Configure the edge pipeline",
      "description": "Add the upstream steps with vault header injection, set allowed origins, and map only the fields the screen binds."
    },
    {
      "stepNumber": 3,
      "title": "Call the pipeline with package:http",
      "description": "Call your workspace edge URL with a restricted rc_live_ client token.",
      "codeSnippet": "final res = await http.get(\n  Uri.parse('https://realcomposer.com/v1/workspace/call-stripe-from-flutter-securely?customerId=$customerId'),\n  headers: {'Authorization': 'Bearer rc_live_client_token'},\n);\nfinal billing = jsonDecode(res.body);"
    }
  ],
  "pipelineConfig": {
    "name": "Call Stripe API from Flutter 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": "plan",
          "sourceStep": "stripe",
          "sourcePath": "subscriptions.data.0.items.data.0.price.nickname"
        },
        {
          "targetPath": "status",
          "sourceStep": "stripe",
          "sourcePath": "subscriptions.data.0.status"
        },
        {
          "targetPath": "renewsAt",
          "sourceStep": "stripe",
          "sourcePath": "subscriptions.data.0.current_period_end",
          "type": "number"
        }
      ]
    }
  },
  "sampleBloatedInput": {
    "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"
                }
              }
            ]
          }
        }
      ]
    }
  },
  "sampleSlimmedOutput": {
    "plan": "Pro",
    "status": "active",
    "renewsAt": 1767225600
  },
  "curlSnippet": "curl -X GET \"https://realcomposer.com/v1/workspace/call-stripe-from-flutter-securely\" \\\n  -H \"Authorization: Bearer rc_live_client_token\"",
  "faqs": [
    {
      "question": "Can I use flutter_stripe without a backend?",
      "answer": "The flutter_stripe SDK uses your publishable key on device, but creating PaymentIntents and reading subscriptions still needs the secret key on a server or edge pipeline."
    },
    {
      "question": "Does --obfuscate protect the Stripe key?",
      "answer": "No. Obfuscation renames symbols but leaves string values intact, so the key can still be extracted."
    }
  ],
  "relatedRecipeSlugs": [
    "call-stripe-from-flutterflow-securely",
    "stripe-proxy-react-native",
    "hide-openai-api-key-flutter"
  ],
  "links": {
    "page": "https://realcomposer.com/recipes/call-stripe-from-flutter-securely",
    "markdown": "https://realcomposer.com/recipes/call-stripe-from-flutter-securely.md",
    "json": "https://realcomposer.com/recipes/call-stripe-from-flutter-securely.json",
    "install": "https://realcomposer.com/dashboard/pipelines/new?recipe=call-stripe-from-flutter-securely&via=agent"
  }
}