Payments
Each workspace connects its own Stripe or PayPal account. Money goes from the respondent to that account; FormFlow never holds funds.
PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET, PAYPAL_WEBHOOK_ID, PAYPAL_ENV) served every form. Those variables are no longer read for form payments. Plan billing for FormFlow itself is separate and still uses STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET.Connect an account
Needs the workspace:manage scope. Credentials are verified with the provider, stored encrypted per workspace and never returned by the API.
| Path | How | Notes |
|---|---|---|
| Stripe restricted or secret key | POST /payments/connections | Body kind stripe_key with secretKey. Register the returned webhookUrl and webhookEvents in Stripe, then send the signing secret with kind stripe_webhook_secret. |
| PayPal | POST /payments/connections | Body kind paypal with clientId, clientSecret, env (sandbox or live) and the webhookId of a webhook you created in PayPal. |
| Stripe Connect | POST /payments/connections/stripe/authorize | Returns an OAuth URL. Works only when the instance operator set STRIPE_CONNECT_CLIENT_ID and STRIPE_CONNECT_WEBHOOK_SECRET. |
curl -X POST https://YOUR_HOST/api/v1/payments/connections \
-H "Authorization: Bearer $FORMFLOW_API_KEY" -H "Content-Type: application/json" \
-d '{"kind":"stripe_key","secretKey":"rk_test_..."}'
# data.webhookUrl and data.webhookEvents: add them in the Stripe dashboard, then
curl -X POST https://YOUR_HOST/api/v1/payments/connections \
-H "Authorization: Bearer $FORMFLOW_API_KEY" -H "Content-Type: application/json" \
-d '{"kind":"stripe_webhook_secret","webhookSecret":"whsec_..."}'Until the webhook is configured a payment stays pending, because the redirect from the provider is not proof of payment. GET /payments/connections reports webhookConfigured and lastError.
Square, Mollie, Braintree, Authorize.Net, Paystack and Mercado Pago
| Provider | Connect with | Checkout and confirmation |
|---|---|---|
| Square | kind square: accessToken, locationId, env, signatureKey | Hosted Payment Link (cards, Apple Pay, Google Pay). Webhook payment.updated verified with HMAC-SHA256 over the exact URL and body. |
| Mollie | kind mollie: apiKey | Hosted checkout (iDEAL, cards, Apple Pay and more). Mollie sends an unsigned id; FormFlow fetches the payment with your key and believes only that response. |
| Paystack | kind paystack: secretKey (sk_test_ sandbox, sk_live_ live) | Hosted checkout in NGN, GHS, ZAR, KES, USD and more. Needs the respondent's email, taken from the form's first email question. Webhook verified with HMAC-SHA512 (x-paystack-signature); events charge.success and refund.processed. |
| Mercado Pago | kind mercadopago: accessToken (TEST- sandbox, APP_USR- live), webhookSecret | Checkout Pro in ARS, BRL, CLP, COP, MXN, PEN and UYU. Notifications verified with x-signature (HMAC-SHA256, 10 minute window), then the payment is fetched with your token. Save the signing secret with kind mercadopago_webhook_secret once the webhook URL is known. |
| Braintree | kind braintree: merchantId, publicKey, privateKey, env | No hosted page: start checkout, tokenize with Braintree's browser SDK using the returned client token, then POST /public/payments/{submissionId}/charge with the nonce. |
| Authorize.Net | kind authorizenet: apiLoginId, transactionKey, env, signatureKey | Tokenize with Accept.js, then POST /public/payments/{submissionId}/charge with dataDescriptor and dataValue. Webhooks verified with X-ANET-Signature (HMAC-SHA512). |
The amount is always the server-priced total stored when checkout started; the charge body cannot change it. These providers handle one-off payments and donations only; subscriptions need Stripe or PayPal. Apple Pay and Google Pay appear on Stripe Checkout, Square and Mollie when enabled in that provider's dashboard; set stripePaymentMethodConfiguration (a pmc_... id) in the form's payment settings to choose which Stripe wallets are offered. The payment field's provider picks the account.
Apple Pay and Google Pay
Choose wallets per provider in PUT /forms/{id}/payment-settings (MCP set_payment_settings): wallets: { square: { applePay, googlePay }, mollie: { applePay }, braintree: { applePay, googlePay }, authorizenet: { googlePay } }. Nothing is on unless you switch it on.
| Provider | What the setting does | Provider-side requirement |
|---|---|---|
| Square | Tells the hosted payment link to offer Apple Pay and Google Pay. | Enable the wallets in the Square dashboard; Apple Pay on a hosted Square link needs no domain file from you. |
| Mollie | applePay adds a shortcut: POST /public/payments/{submissionId}/checkout?wallet=applePay opens Mollie's page on Apple Pay. Mollie has no Google Pay. | Enable Apple Pay in the Mollie dashboard (Settings, Payment methods). The hosted page needs no domain file from you. |
| Braintree | After submit the form shows Apple Pay and Google Pay buttons that tokenize with Braintree's browser SDK; the server charges the stored amount. | Apple Pay: register your form domain in the Braintree control panel (Settings, Processing, Apple Pay) and host Apple's domain association file at /.well-known/apple-developer-merchantid-domain-association on that domain. Google Pay: production needs Google's merchant approval. |
| Authorize.Net | Google Pay button; the Google Pay token is sent to the charge endpoint as dataDescriptor COMMON.GOOGLE.INAPP.PAYMENT. | Google Pay merchant approval for production. Apple Pay is not offered: it needs merchant validation with an Apple certificate that FormFlow does not hold. |
| Stripe | Informational. Stripe's wallets come from stripePaymentMethodConfiguration. | Verify the domain in the Stripe dashboard (Settings, Payment method domains). |
The wallet buttons need the browser to allow scripts from the provider (Braintree, Google Pay); on a page that blocks them the buttons simply do not appear. Wallet flows are not verified against live accounts.
Tax, shipping and subscriptions
PUT /forms/{id}/payment-settings (form:write) sets rules per form: a tax rate with optional regions taken from an answer, shipping methods with free-over thresholds, and a subscription interval with an optional trial. Respondents see the same numbers the checkout charges from through POST /public/forms/{slug}/quote.
Reconcile, sync and refund
GET /payments lists payments with mismatches flagged (paid without a response, amount differences). POST /payments/{id}/sync asks the provider for the current state and repairs a missed webhook. POST /payments/{id}/refund needs confirmAmountMinor equal to the amount refunded, so a stale screen cannot refund a different sum. Subscription charges are refunded in the provider dashboard.
What FormFlow does not claim
Card data never reaches FormFlow: respondents pay on Stripe Checkout or PayPal hosted pages, and FormFlow stores provider ids, amounts and statuses. FormFlow makes no PCI claim and no certification claim. Every provider path is unverified against the live services; see Status.