Skip to main content
RapidDev - Software Development Agency
flutterflow-integrationsFlutterFlow API Call

Adyen

Connect FlutterFlow to Adyen via a Firebase Cloud Function or Supabase Edge Function proxy that creates Adyen Checkout Sessions using an X-API-Key header — the API key never touches your Flutter client. The app receives session data and client key from the proxy, then renders payment via Adyen Drop-in in a Custom Widget or redirects to Adyen's hosted payment page. All amounts must be in minor units (1999 = $19.99).

What you'll learn

  • Why Adyen is an enterprise-grade processor with volume minimums — and when it's the right choice for a FlutterFlow app
  • How to deploy a Firebase Cloud Function proxy that creates Adyen Checkout Sessions using the X-API-Key header
  • How to configure a FlutterFlow API Group calling your proxy and binding amount/currency/reference variables
  • The critical minor-units requirement: why 1999 means $19.99 and how to wire this correctly to FlutterFlow variables
  • How to receive HMAC-signed Adyen webhook notifications and stream payment status into your app
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Advanced21 min read4–6 hoursPaymentsLast updated July 2026RapidDev Engineering Team
TL;DR

Connect FlutterFlow to Adyen via a Firebase Cloud Function or Supabase Edge Function proxy that creates Adyen Checkout Sessions using an X-API-Key header — the API key never touches your Flutter client. The app receives session data and client key from the proxy, then renders payment via Adyen Drop-in in a Custom Widget or redirects to Adyen's hosted payment page. All amounts must be in minor units (1999 = $19.99).

Quick facts about this guide
FactValue
ToolAdyen
CategoryPayments
MethodFlutterFlow API Call
DifficultyAdvanced
Time required4–6 hours
Last updatedJuly 2026

When to Use Adyen in a FlutterFlow App — and What to Expect

Adyen is one of the most powerful payment platforms on earth — used by Uber, Spotify, McDonald's, and eBay — but it's explicitly built for high-volume enterprise merchants. Historically, Adyen has required minimum transaction volumes to access their platform and pricing is interchange-plus rather than flat-rate, which means it's typically not the first choice for a small startup's FlutterFlow app. Stripe, Braintree, or Square cover the majority of use cases at smaller scale. That said: if your FlutterFlow app is a white-label tool for a large enterprise client, an enterprise B2B marketplace, or a retail chain with an existing Adyen relationship, this integration is exactly what you need.

The core integration pattern is Adyen's Sessions flow: your backend creates a payment session using POST /v71/sessions with your X-API-Key in the header, and Adyen returns a session ID and session data that the client app can use to render payment — either via the native Adyen Drop-in UI (a Custom Widget in FlutterFlow) or via a redirect to Adyen's hosted payment page (simpler, no Drop-in widget needed). Every amount is expressed in minor currency units: $19.99 = 1999, €12.50 = 1250. This is the single most common source of bugs for developers new to Adyen — wiring a dollars value where Adyen expects cents results in charging 100× too little or too much.

Environment management is also explicit and strict with Adyen: the test host is checkout-test.adyen.com/v71 and the live host follows a pattern like your-merchant-prefix-checkout-live.adyenpayments.com/checkout/v71 — not a simple sandbox vs. production URL swap. You'll also need to specify the correct API version (/v71 at the time of writing — verify current version in Adyen docs). Mixing test credentials with live hosts or wrong version paths returns 401 or 404 errors that look like auth failures.

Integration method

FlutterFlow API Call

Your FlutterFlow app calls your own backend proxy (Firebase Cloud Function or Supabase Edge Function) to create an Adyen Checkout Session. The proxy calls Adyen's /v71/sessions endpoint with the X-API-Key header, returns the session data and client key to the app, and the app uses that to render payment via a Custom Widget or redirect to a hosted page. Payment result notifications (webhooks) arrive at the backend HMAC-verified, and the app reads the final status from Firestore or Supabase.

Prerequisites

  • An Adyen test account (create one at adyen.com — free for test) with a test API Key, client key, and merchant account name from the Adyen Customer Area
  • A Firebase project with Cloud Functions enabled (Blaze plan) OR a Supabase project with Edge Functions
  • A FlutterFlow project (Custom Code / Custom Widget features are available on paid plans)
  • Basic understanding of webhook concepts — Adyen sends payment results to your backend, not your mobile app
  • Knowledge of your final live Adyen environment hostname (your-merchant-checkout-live.adyenpayments.com) — available in the Adyen Customer Area under Account settings once your live account is approved

Step-by-step guide

1

Create an Adyen Test Account and Retrieve Your Credentials

Start at adyen.com and sign up for a test account — Adyen's test environment is free to create and doesn't require a formal business contract (unlike the live environment, which goes through a sales process with volume discussions). Once your test account is active, log in to the Adyen Customer Area (ca-test.adyen.com). You need three pieces of information for the integration: 1. API Key — go to Developers → API credentials → your credential → click 'Generate API key'. This key goes in the X-API-Key header for all server-to-Adyen calls. It's a server secret and must never appear in your Flutter app or FlutterFlow project. 2. Client Key — on the same API credentials screen, find the Client key field. The client key is safe to use in client-side code (it's designed for that purpose) — it's what the Adyen Drop-in SDK uses to initialize the payment UI. You'll pass this from your proxy to your FlutterFlow app. 3. Merchant Account — your merchant account name is displayed in the top-left of the Customer Area (usually your company name in ALLCAPS format, like YOURCOMPANYNB). You'll include it in every /v71/sessions request body. Also note the API version currently documented: at the time of writing, Adyen's Checkout API is at /v71, but Adyen increments this periodically. Verify the current version at docs.adyen.com/api-explorer before hardcoding /v71 in your proxy — using an outdated version path returns 404 even with valid credentials. Store the API Key in your Cloud Function's environment variables as ADYEN_API_KEY, the client key as ADYEN_CLIENT_KEY, and the merchant account as ADYEN_MERCHANT_ACCOUNT. None of these go in FlutterFlow.

Pro tip: In the Adyen Customer Area, make sure your test API key has the Checkout: Write scope enabled under the 'Roles' section — without it, POST /v71/sessions returns a 403 'Forbidden' even with a valid key.

Expected result: You have a test API Key, client key, and merchant account name from the Adyen Customer Area. You can test the API key manually via curl: POST to https://checkout-test.adyen.com/v71/sessions with the X-API-Key header and a minimal body — you should receive a 200 with a session object.

2

Deploy a Firebase Cloud Function Proxy for Adyen Session Creation

As with any payment integration in FlutterFlow, the Adyen API key is a server secret — placing it in a FlutterFlow API Call header embeds it in your compiled Flutter app, where it can be extracted. A leaked Adyen API key gives an attacker full access to create payment sessions under your merchant account, which could lead to fraudulent transactions billed to real customers. Deploy a Firebase Cloud Function (or Supabase Edge Function) that accepts a payout request from your app (amount in minor units, currency, reference, shopperEmail) and calls Adyen's /v71/sessions endpoint. The function returns the session's id, sessionData, and your clientKey to the app — these are safe to pass to the client for rendering the Drop-in UI. The reference field in the Adyen sessions request is critical: it must be unique per transaction and is how you match Adyen's webhook notification back to the order in your database. Use a UUID or your Firestore/Supabase document ID as the reference. When Adyen's webhook fires with a payment result, it includes this reference so you can look up and update the right order record. Note the amount structure: Adyen requires amount as an object with value (integer in minor units) and currency (ISO 4217 3-letter code). $19.99 = { value: 1999, currency: 'USD' }. $100 = { value: 10000, currency: 'USD' }. This is the most common beginner bug — sending 19.99 as a float instead of 1999 as an integer. For the proxy function below, read the ADYEN_API_KEY, ADYEN_MERCHANT_ACCOUNT, and ADYEN_CLIENT_KEY from process.env. The base URL https://checkout-test.adyen.com/v71 is for test — swap it to your live URL via an env var before going live.

index.js
1// Firebase Cloud Function: functions/index.js
2const functions = require('firebase-functions');
3const admin = require('firebase-admin');
4const axios = require('axios');
5const { v4: uuidv4 } = require('uuid');
6
7admin.initializeApp();
8
9const ADYEN_BASE_URL = process.env.ADYEN_BASE_URL || 'https://checkout-test.adyen.com/v71';
10const ADYEN_API_KEY = process.env.ADYEN_API_KEY;
11const ADYEN_MERCHANT_ACCOUNT = process.env.ADYEN_MERCHANT_ACCOUNT;
12const ADYEN_CLIENT_KEY = process.env.ADYEN_CLIENT_KEY;
13
14exports.createAdyenSession = functions.https.onRequest(async (req, res) => {
15 // CORS for FlutterFlow web preview
16 res.set('Access-Control-Allow-Origin', '*');
17 res.set('Access-Control-Allow-Methods', 'POST, OPTIONS');
18 res.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
19 if (req.method === 'OPTIONS') return res.status(204).send('');
20 if (req.method !== 'POST') return res.status(405).send('Method Not Allowed');
21
22 const { amountValue, currency, returnUrl, shopperEmail } = req.body;
23 if (!amountValue || !currency || !returnUrl) {
24 return res.status(400).json({ error: 'amountValue (integer minor units), currency, and returnUrl are required' });
25 }
26
27 const reference = uuidv4(); // unique per transaction
28
29 try {
30 const response = await axios.post(
31 `${ADYEN_BASE_URL}/sessions`,
32 {
33 merchantAccount: ADYEN_MERCHANT_ACCOUNT,
34 amount: { value: amountValue, currency },
35 reference,
36 returnUrl,
37 shopperEmail: shopperEmail || undefined,
38 },
39 { headers: { 'X-API-Key': ADYEN_API_KEY, 'Content-Type': 'application/json' } }
40 );
41
42 // Optionally write pending order to Firestore here
43 const db = admin.firestore();
44 await db.collection('orders').doc(reference).set({
45 reference,
46 amountValue,
47 currency,
48 shopperEmail: shopperEmail || null,
49 status: 'PENDING',
50 createdAt: admin.firestore.FieldValue.serverTimestamp(),
51 });
52
53 return res.status(200).json({
54 sessionId: response.data.id,
55 sessionData: response.data.sessionData,
56 reference,
57 clientKey: ADYEN_CLIENT_KEY,
58 });
59 } catch (error) {
60 const status = error.response?.status || 500;
61 return res.status(status).json({ error: error.response?.data || error.message });
62 }
63});

Pro tip: The returnUrl in the session request is where Adyen redirects the user after the hosted payment page completes — for a mobile app this should be a deep link back into your app (e.g., yourapp://payment-result?reference={{reference}}). For a redirect-less Drop-in widget flow, you still need a valid returnUrl in case the user is redirected for 3DS authentication.

Expected result: Deploying the function gives you a URL like https://us-central1-your-project.cloudfunctions.net/createAdyenSession. Testing it with POST {"amountValue": 1999, "currency": "USD", "returnUrl": "https://example.com"} returns a 200 with sessionId, sessionData, reference, and clientKey.

3

Configure a FlutterFlow API Group to Call Your Proxy

In FlutterFlow, click API Calls in the left navigation panel. Click + Add and select Create API Group. Name it AdyenProxy. Set the Base URL to your deployed Firebase Function's root URL — for example, https://us-central1-your-project.cloudfunctions.net. Leave the headers empty (no API keys here — those are all in the proxy). Inside the AdyenProxy group, click + Add and select Create API Call. Name it CreateSession. Set the Method to POST and the Endpoint Path to /createAdyenSession. Now click the Variables tab. Add the following variables: - amountValue (Integer) — the payment amount in minor currency units (1999 for $19.99) - currency (String) — ISO 4217 code ('USD', 'EUR', 'GBP') - returnUrl (String) — the URL Adyen redirects to after the hosted payment page - shopperEmail (String) — optional; pass an empty string if not collecting email In the Body tab, set the content type to JSON and write the body template: {"amountValue": {{amountValue}}, "currency": "{{currency}}", "returnUrl": "{{returnUrl}}", "shopperEmail": "{{shopperEmail}}"} Note that amountValue must be an integer with no quotes — {{amountValue}} not "{{amountValue}}". This is critical: if currency units arrive as a quoted string, the Adyen API will reject the request with a 400 validation error. Click the Response & Test tab. Paste a sample success response from your proxy (the JSON object with sessionId, sessionData, reference, clientKey). Click Generate JSON Paths. Add paths for: - $.sessionId → the Adyen session ID - $.sessionData → opaque session data for the Drop-in SDK - $.reference → your order reference (for tracking) - $.clientKey → the Adyen client key Fill in sample variable values and click Test. You should see a live 200 response with real Adyen test session data. If you see a 400 error about the amount, check that amountValue is being sent as a number, not a string.

typescript
1{
2 "method": "POST",
3 "endpoint": "/createAdyenSession",
4 "body": {
5 "amountValue": {{amountValue}},
6 "currency": "{{currency}}",
7 "returnUrl": "{{returnUrl}}",
8 "shopperEmail": "{{shopperEmail}}"
9 },
10 "response_paths": {
11 "session_id": "$.sessionId",
12 "session_data": "$.sessionData",
13 "reference": "$.reference",
14 "client_key": "$.clientKey"
15 }
16}

Pro tip: The amountValue variable should be an Integer type in FlutterFlow, not Double or String. If the user enters '19.99' in a text field, multiply by 100 and truncate to an integer in a Set Variable action before calling the API — don't let floating-point values reach the Adyen API.

Expected result: The CreateSession API Call shows a green success indicator in the Response & Test tab with a real Adyen test session response including sessionId, sessionData, reference, and clientKey.

4

Render Payment via Adyen Hosted Page or Drop-in Custom Widget

Once your FlutterFlow app has a sessionId and sessionData from the proxy, there are two ways to render the payment UI. The simpler approach is a hosted payment page redirect — Adyen provides a URL where you send the user to complete payment in a browser. The more integrated approach is the Adyen Drop-in widget rendered directly in your app via a Custom Widget. APPROACH 1 — Hosted payment page (recommended for most FlutterFlow apps): Adyen's hosted payment page URL follows the format: https://pay.adyen.com/checkout/{version}/{session_id}?sessionData={encoded_session_data}. After your CreateSession API Call completes, add a 'Launch URL' action (or url_launcher Custom Action) that opens this URL. The user completes payment in a browser tab, and Adyen redirects them to your returnUrl with query parameters indicating the result. You can intercept this with a deep link handler Custom Action or simply show a 'waiting for payment confirmation' screen and poll your Firestore collection until the webhook updates the order status. APPROACH 2 — Adyen Drop-in Custom Widget: For a fully in-app payment experience, add the Adyen Flutter SDK as a Custom Widget. In FlutterFlow, click Custom Code in the left nav → + Add → Widget. Name it AdyenDropIn. In the Dependencies field, search for and add adyen_checkout (the official Adyen pub.dev package — verify the current version). In the Widget Dart code, implement a StatefulWidget that initializes AdyenCheckout with the session ID, session data, and client key, and calls the Drop-in UI. This approach requires testing on a physical device (not in FlutterFlow's web Run Mode), and the Drop-in will not render in the FlutterFlow canvas preview. For most teams building their first Adyen integration in FlutterFlow, the hosted payment page is the right starting point — it requires no Custom Widget code, works on web and mobile equally, and is easier to test. You can always migrate to Drop-in later once the payment flow is working end-to-end.

custom_action.dart
1// Custom Action: open Adyen hosted payment page
2// Add 'url_launcher' in Custom Code dependencies
3import 'package:url_launcher/url_launcher.dart';
4
5Future openAdyenHostedPage(
6 String sessionId,
7 String sessionData,
8) async {
9 // Adyen hosted page URL — use /v71/ or the version matching your API calls
10 final encodedSessionData = Uri.encodeComponent(sessionData);
11 final url = 'https://pay.adyen.com/checkout/v71/$sessionId?sessionData=$encodedSessionData';
12 final uri = Uri.parse(url);
13 if (await canLaunchUrl(uri)) {
14 await launchUrl(uri, mode: LaunchMode.externalApplication);
15 } else {
16 throw Exception('Could not launch Adyen payment page');
17 }
18}

Pro tip: Add url_launcher in the Custom Code dependencies field in FlutterFlow rather than a pubspec.yaml — FlutterFlow manages dependencies visually. Search for 'url_launcher' in the Dependencies field and select the package; FlutterFlow adds it to the build automatically.

Expected result: Tapping the 'Pay Now' button calls the CreateSession API Call, receives sessionId and sessionData from the proxy, and opens the Adyen hosted payment page in an external browser where you can complete a test payment using Adyen's test card numbers (e.g., 4111 1111 1111 1111).

5

Add an HMAC-Verified Webhook Endpoint and Stream Status to the App

After a payment completes (or fails), Adyen sends an HMAC-signed webhook notification to your backend — this is the authoritative source of payment truth. The app UI should update based on this webhook, not on the session creation response. In the Adyen Customer Area, go to Developers → Webhooks → + Standard webhook. Set the URL to your Firebase/Supabase webhook function URL (you'll create this now). Set the HMAC key — Adyen generates one for you. Store this HMAC key in your Cloud Function's environment as ADYEN_HMAC_KEY. Deploy a second Cloud Function (adyenWebhook) that: (1) reads the incoming notification body, (2) verifies the HMAC signature using Adyen's HMAC validation algorithm, (3) extracts the merchantReference (your order reference) and the success/failure status, (4) updates the matching Firestore document's status field, and (5) returns a 200 '[accepted]' response to Adyen — Adyen retries webhooks if it doesn't receive this acknowledgment within 10 seconds. In FlutterFlow, bind a list widget or a status text widget to a Firestore Collection or Supabase real-time subscription for the order document keyed by the reference you stored in Step 2. As soon as the webhook fires and updates Firestore, the FlutterFlow widget re-renders with the updated status — no polling required. A unique and consistent reference per transaction (the UUID you generated in the proxy) is what makes this match work. If you reuse references or generate them differently on each retry, the webhook update won't find the right Firestore document. If you'd rather skip setting up the webhook infrastructure and just want payment sessions running in FlutterFlow quickly, RapidDev builds these integrations end-to-end — free scoping call at rapidevelopers.com/contact.

index.js
1// Firebase Cloud Function: adyenWebhook
2const crypto = require('crypto');
3
4exports.adyenWebhook = functions.https.onRequest(async (req, res) => {
5 if (req.method !== 'POST') return res.status(405).send('Method Not Allowed');
6
7 const db = admin.firestore();
8 const HMAC_KEY = process.env.ADYEN_HMAC_KEY;
9
10 try {
11 const notificationItems = req.body.notificationItems || [];
12 for (const item of notificationItems) {
13 const notification = item.NotificationRequestItem;
14 const { pspReference, merchantReference, success, eventCode, additionalData } = notification;
15
16 // Verify HMAC signature
17 const hmacSignature = additionalData?.hmacSignature;
18 if (!hmacSignature) continue;
19
20 // Simple HMAC check (use Adyen's SDK or reference implementation for production)
21 const dataToSign = [
22 notification.pspReference,
23 notification.originalReference || '',
24 notification.merchantAccountCode,
25 notification.merchantReference,
26 notification.amount.value,
27 notification.amount.currency,
28 notification.eventCode,
29 notification.success
30 ].join(':');
31
32 const expectedHmac = crypto
33 .createHmac('sha256', Buffer.from(HMAC_KEY, 'hex'))
34 .update(dataToSign)
35 .digest('base64');
36
37 if (expectedHmac !== hmacSignature) {
38 console.warn('HMAC mismatch — possible tampered webhook');
39 continue;
40 }
41
42 // Update Firestore order
43 await db.collection('orders').doc(merchantReference).update({
44 status: success === 'true' ? 'PAID' : 'FAILED',
45 pspReference,
46 eventCode,
47 updatedAt: admin.firestore.FieldValue.serverTimestamp(),
48 });
49 }
50 return res.status(200).send('[accepted]');
51 } catch (error) {
52 console.error('Webhook error:', error);
53 return res.status(500).send('Error');
54 }
55});

Pro tip: Always return 200 '[accepted]' to Adyen as quickly as possible — Adyen retries webhooks if it doesn't get an acknowledgment within ~10 seconds. Do your Firestore writes asynchronously or after sending the 200 if you experience timeouts.

Expected result: After completing a test payment on the Adyen hosted page, the adyenWebhook function logs show the notification arriving, the HMAC validates, and the Firestore order document updates to status: 'PAID'. The FlutterFlow app widget bound to that Firestore document reflects 'Payment Confirmed' without any manual refresh.

6

Switch from Test Environment to Live Adyen Environment

Adyen's live environment has a different URL pattern from the test environment — this is one of the most confusing things about Adyen for developers accustomed to a simple sandbox/production base-URL swap. Test endpoint: https://checkout-test.adyen.com/v71 Live endpoint: https://{prefix}-checkout-live.adyenpayments.com/checkout/v71 The {prefix} is specific to your merchant account and is only assigned once Adyen approves your live account. You'll find it in the Adyen Customer Area under Account → Company Account → checkout endpoint. There is no generic 'checkout-live.adyen.com' — you must use your merchant-specific hostname. To switch: set the ADYEN_BASE_URL environment variable in your Cloud Function to your live endpoint hostname. Also update ADYEN_API_KEY to your live API key (generated in the live Customer Area — test keys don't work on live endpoints). Update ADYEN_CLIENT_KEY to the live client key. Redeploy the function. In FlutterFlow, no changes are needed if you've built the API Group pointing to your Cloud Function URL — the function is the single point of environment switching. This is exactly why centralizing all Adyen calls in the proxy is valuable: a single env var change flips the entire integration to live. Before flipping to live, confirm: (1) your Adyen live account is fully onboarded and KYC-completed; (2) your webhook endpoint is configured in the live Customer Area (not just the test one); (3) your HMAC key is updated for the live webhook; and (4) you've done a real low-value test transaction through the live environment with a real card before opening to users.

Pro tip: Adyen's live API version path (/v71) must match the test version you've been using — if Adyen releases /v72 between your test and live release, make sure both environments use the same version. Check docs.adyen.com/api-explorer for the current recommended version.

Expected result: With ADYEN_BASE_URL set to the live merchant endpoint and live API key/client key in place, a test transaction through the live environment (using a real card at a minimal amount like $1) succeeds and the Firestore order document shows 'PAID' via the live webhook.

Common use cases

Enterprise retail app processing card payments across multiple store locations

A large retail chain uses FlutterFlow to build a mobile point-of-sale companion app for store associates. The app creates an Adyen checkout session server-side, presents the payment via a hosted payment page redirect, and monitors payment status in real time from a Firestore collection updated by Adyen webhooks. The unique reference per transaction links the webhook notification back to the order in the app.

FlutterFlow Prompt

Build a checkout screen in FlutterFlow that takes an order amount, creates an Adyen payment session via my backend proxy, opens the hosted payment page for the customer to pay, and then shows a 'Payment Received' confirmation screen when the webhook updates the order status in Firestore.

Copy this prompt to try it in FlutterFlow

B2B marketplace with global local payment methods

A FlutterFlow-built B2B marketplace serves buyers in Europe, Southeast Asia, and Latin America who need to pay via local methods (iDEAL in Netherlands, PayNow in Singapore, Pix in Brazil). Adyen supports all of these through the same Sessions API — the app passes the country and preferred payment method, and Adyen's Drop-in UI renders the appropriate local payment flow. The backend tracks each transaction via a unique reference and stores Adyen webhook outcomes in Supabase.

FlutterFlow Prompt

Create a payment screen in FlutterFlow where users select a payment method from a list returned by my Adyen proxy, initiate a payment session, see the Adyen Drop-in UI embedded as a Custom Widget, and receive confirmation when my Supabase table updates with the payment result.

Copy this prompt to try it in FlutterFlow

Subscription billing platform with recurring payment setup

A SaaS platform built in FlutterFlow lets enterprise customers set up recurring payments via Adyen's tokenization. The initial payment flow creates an Adyen session with shopperInteraction=ContAuth and stores the returned recurringDetailReference server-side. Subsequent charges are triggered automatically from the backend on billing cycles, and the app simply displays the subscription status and payment history read from Supabase.

FlutterFlow Prompt

Design a subscription setup flow in FlutterFlow where the user enters payment details via the Adyen Drop-in widget, my backend stores the recurring token, and a 'Subscription Active' screen shows the billing frequency and next payment date from Supabase.

Copy this prompt to try it in FlutterFlow

Troubleshooting

POST /v71/sessions returns 401 Unauthorized

Cause: The X-API-Key is wrong, expired, or doesn't have the Checkout: Write permission scope. Also occurs when test credentials are sent to a live endpoint URL.

Solution: In the Adyen Customer Area, go to Developers → API credentials → your credential and confirm the API key is active and has the 'Checkout: Write' role. Regenerate the key if needed and update it in your Cloud Function's environment variables. Confirm your ADYEN_BASE_URL is the test URL (checkout-test.adyen.com) if you're using test credentials.

Payment amount is 100x too small — $19.99 product charges $0.20

Cause: The amountValue was sent as 19 (dollars as integer) or 19.99 (dollars as float) instead of 1999 (cents as integer). Adyen interprets all values as minor currency units.

Solution: In FlutterFlow, add a Set Variable action before the API Call that converts the display price to minor units: multiply the dollar amount by 100 and round to the nearest integer. Bind the resulting integer variable to the amountValue API Call variable — never pass a floating-point dollar value.

typescript
1// In a FlutterFlow Custom Action or formula:
2// Convert dollar amount to minor units
3int toMinorUnits(double dollarAmount) {
4 return (dollarAmount * 100).round();
5}
6// Example: toMinorUnits(19.99) returns 1999

POST /sessions returns 404 Not Found

Cause: The API version path is wrong (e.g., /v68 instead of /v71) or you're using a generic live URL instead of your merchant-specific live endpoint hostname.

Solution: Verify the current Adyen Checkout API version at docs.adyen.com/api-explorer and update your ADYEN_BASE_URL accordingly. For live, use your merchant-specific hostname from the Adyen Customer Area (Account → Company Account → checkout endpoint) — not a generic adyen.com URL.

Webhook notifications arrive but HMAC verification fails

Cause: The HMAC key is wrong (test vs live mismatch), the data-to-sign string is constructed in the wrong field order, or the notification body was parsed as JSON before HMAC verification (Adyen signs the raw string).

Solution: Confirm you're using the HMAC key from the same Customer Area environment (test or live) where the webhook is configured. Check Adyen's documentation for the exact field order in the HMAC data string — it's specific and order-sensitive. Adyen provides a test webhook in the Customer Area where you can fire a notification and see the expected HMAC value to compare against your implementation.

'XMLHttpRequest error' in FlutterFlow's web Run Mode when testing the API Call

Cause: The Cloud Function doesn't return CORS headers, so the browser blocks the response from a different origin in FlutterFlow's web preview.

Solution: Add CORS headers to your Cloud Function response (as shown in the proxy code in Step 2: Access-Control-Allow-Origin: *, handle OPTIONS preflight). Native iOS/Android builds from FlutterFlow don't enforce CORS — only web preview and web builds do.

Best practices

  • Never put the Adyen X-API-Key in a FlutterFlow API Call header or Dart custom action — always route session creation through a Firebase Cloud Function or Supabase Edge Function proxy.
  • Always express amounts in minor currency units (integer cents/pence/etc.) — $19.99 = 1999, not 19.99. Multiply dollar values by 100 and round to integer before passing to the API.
  • Use a unique UUID or Firestore/Supabase document ID as the Adyen transaction reference so webhook notifications can be matched back to the exact order in your database.
  • Manage the test vs. live environment switch entirely through Cloud Function environment variables (ADYEN_BASE_URL, ADYEN_API_KEY, ADYEN_CLIENT_KEY) so you can flip without code changes or redeployment of FlutterFlow.
  • Verify the current Adyen Checkout API version (/v71 or later) in the Adyen API Explorer before building — version path mismatches look like 404 auth errors and waste debugging time.
  • Respond to Adyen webhooks with 200 '[accepted]' within 10 seconds — do Firestore/Supabase writes asynchronously if needed, as Adyen retries unacknowledged webhooks and duplicate notifications can cause double-update bugs.
  • Test the HMAC signature verification in the Adyen Customer Area's test-webhook tool before opening to production traffic — a misconfigured HMAC check means you're accepting unverified (potentially spoofed) payment notifications.
  • Evaluate Adyen's pricing and volume requirements honestly for your app stage — for most early-stage FlutterFlow apps, Stripe or Braintree are more appropriate starting points; consider Adyen when you have confirmed enterprise requirements or an existing Adyen relationship.

Alternatives

Frequently asked questions

Does Adyen have volume minimums? Can I use it for a small FlutterFlow app?

Historically yes — Adyen has required minimum transaction volumes as part of their merchant contracts, and their pricing is typically interchange-plus rather than the flat 2.9%+30¢ model of Stripe or Braintree. For most early-stage FlutterFlow apps or solo-founder projects, Stripe or Braintree is a better starting point. Adyen makes sense when you're building for an enterprise client with existing Adyen infrastructure, need specific local payment methods in emerging markets, or have confirmed high transaction volumes. Check with Adyen's sales team for current requirements — they may have changed.

Can I use Adyen in FlutterFlow without a backend proxy?

Not safely. The Adyen X-API-Key is a server secret that grants full access to create payment sessions under your merchant account. If you place it in a FlutterFlow API Call header, it ships inside the compiled Flutter app binary and can be extracted. Always deploy a Firebase Cloud Function or Supabase Edge Function to hold the key and proxy session creation to Adyen. Your Flutter app should only ever receive the sessionId, sessionData, and client key — never the API key itself.

How do I know if a payment succeeded — from the session response or a webhook?

From the webhook. The /v71/sessions response only tells you a session was created, not whether payment was completed. Adyen sends the authoritative payment result via a webhook notification (AUTHORIZATION event) after the customer submits payment. Your Firebase/Supabase function verifies the HMAC signature, updates the order status in your database, and your FlutterFlow app reads the updated status from there via a real-time listener. Never assume payment succeeded based on the session creation response alone.

Why is the Adyen live URL different from the test URL?

Adyen uses merchant-specific live endpoints (e.g., yourmerchant-checkout-live.adyenpayments.com) rather than a generic live URL, as part of their data residency and routing architecture. Your live prefix is assigned when Adyen approves your live account and is visible in the Adyen Customer Area under Account settings. There's no way to guess it — you must retrieve it from your Customer Area before going live.

Does the Adyen Drop-in widget work in FlutterFlow's web preview (Run Mode)?

No. The Adyen Drop-in in a Custom Widget/Action uses native SDK code that doesn't run in FlutterFlow's web-based Run Mode or canvas preview. You need to test it on a physical device (iOS or Android) or via a full local Flutter build. The simpler hosted payment page approach (opening the Adyen pay.adyen.com URL in a browser) does work from the FlutterFlow Run Mode since it opens an external browser tab rather than rendering native code inside the preview.

RapidDev

Talk to an Expert

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

Book a free consultation

Integrations are where projects stall

We wire FlutterFlow integrations like this one every week — auth, webhooks, edge cases included. Fixed-price builds from $13K, delivered in 6–10 weeks.

Talk to an integration engineer

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.