How to Build a Custom Payments Integration on Sparkforge
Build a custom payment provider by creating a Marketplace app, implementing OAuth and a queryUrl backend, hosting a checkout iframe at paymentsUrl, and sending webhooks back to Sparkforge. The verify response — not webhooks — sets the initial subscription state.
Sparkforge’s custom payment provider framework lets you plug any gateway into the platform and use it everywhere Sparkforge processes payments — checkouts, subscriptions, saved cards, refunds, off-session charges. This guide walks the full build.
What you can do once integrated
- Show up in the App Marketplace and Payments → Integrations.
- Power one-time and recurring payments.
- Run off-session charges with stored payment methods.
- Support manual subscriptions and subscription schedules.
- Sync state to Sparkforge via webhooks.
Key concepts
- Marketplace App — the container for your integration: OAuth, scopes, category, custom pages.
- Custom Payment Provider — the payments config telling Sparkforge your app is a payment provider and which payment types you support.
- queryUrl — your backend endpoint Sparkforge calls for server-side ops:
verify,list_payment_methods,charge_payment,create_subscription,cancel_subscription,refund. - paymentsUrl — the public URL Sparkforge loads in an iframe to collect payments.
- Custom Page — the public URL Sparkforge loads in an iframe in the Marketplace UI for config (API keys, merchant IDs, modes).
- Test vs Live mode — each location holds separate config for both.
Prerequisites
- A Sparkforge account with Marketplace Dashboard access.
- A backend service in any language (Node, Python, Ruby, etc.).
- Public domains for OAuth redirect, custom page, queryUrl, webhooks, and paymentsUrl.
- Working knowledge of OAuth 2.0, JSON HTTP APIs, and webhooks.
Step 1 — Create your Marketplace App
Go to marketplace.sparkforge.com and create an app. Configure four sections: Settings, Payment Provider, Profile, Custom Pages.
Required scopes
payments/orders.readonly
payments/orders.write
payments/subscriptions.readonly
payments/transactions.readonly
payments/custom-provider.readonly
payments/custom-provider.write
products.readonly
products/prices.readonlyRedirect URL
Sparkforge redirects users here with a code query parameter on install. Exchange the code for an access token using the OAuth Access Token API and store the token securely.
Client keys
Sparkforge issues OAuth client keys. Keep them server-side only and use them when exchanging the code for a token.
Webhook URL (install/uninstall)
Sparkforge calls this when your app installs at or uninstalls from a location. Use it to bootstrap or tear down per-location config.
SSO key
Stored on your backend, used to decrypt the auth token Sparkforge passes to your custom page so you can identify the user and location.
Payment Provider config
- Name — display name in Sparkforge UI.
- App description — short text on Payments → Integrations.
- Provider type — OneTime, Recurring, Off Session (one or more).
- Logo — public URL for the icon.
Profile
Set Category = Third Party Provider. This places your app correctly in the App Marketplace and Payments → Integrations.
Custom Page
The iframe-loaded config page for collecting gateway credentials. Used post-install and again when a user clicks Manage Integration under Payments → Integrations.
Step 2 — Install flow and provider config
On install:
- Sparkforge calls your redirect URL with a
code. - Your backend exchanges the code for an access token.
- Sparkforge loads your custom page.
- Your backend calls the Create Public Provider Config API to register the provider.
{
"name": "My Payment Provider",
"description": "Tagline",
"imageUrl": "https://your-domain.com/logo.png",
"locationId": "SUB_ACCOUNT_ID",
"queryUrl": "https://your-backend.com/payments/query",
"paymentsUrl": "https://your-frontend.com/payments/checkout"
}Step 3 — Connect test and live keys
For each mode, call the Connect Config API with:
apiKey— backend secret used to authenticate Sparkforge → your queryUrl.publishableKey— frontend-safe key for the iframe.
Once test or live keys are present, that mode becomes usable. Users set the provider as default at Payments → Integrations → [Your App] → Set as Default.
Step 4 — Implement the checkout iframe
Ready event
{ "type": "custom_provider_ready", "loaded": true }Payment initiate
Sparkforge sends a payment_initiate_props event with publishable key, amount, currency, mode (payment or subscription), productDetails, contact (id, name, email, phone, shipping address), and IDs for order, transaction, subscription, and location. Use it to start the gateway flow.
Outcome events
- Success:
{ "type": "custom_element_success_response", "chargeId": "GATEWAY_CHARGE_ID" } - Failure:
{ "type": "custom_element_error_response", "error": { "description": "Card was declined" } } - Canceled:
{ "type": "custom_element_close_response" }
Verify call
After a success event, Sparkforge POSTs type: "verify" to your queryUrl with transactionId, chargeId, optional subscriptionId, and your apiKey. Respond with one of:
{ "success": true }— marks the order/transaction successful.{ "failed": true }— marks failed.{ "success": false }— leaves it pending; you can update later via webhooks.
Step 5 — Saved payment methods and manual subscriptions
Add card on file
Set addCardOnFileSupported: true on the ready event. Sparkforge then sends setup_initiate_props for setup-mode flows. Reply with success or error events the same way.
List payment methods
Sparkforge calls queryUrl with type: "list_payment_methods" and a contactId. Return an array of { id, type, title, subTitle, expiry, customerId, imageUrl }. You’re responsible for keeping the contact ↔ customer mapping on your side.
Charge a saved method
For off-session charges, Sparkforge sends type: "charge_payment" with paymentMethodId, contactId, transactionId, chargeDescription, amount, currency, and apiKey. Return success, chargeId, and a chargeSnapshot containing status, amount, chargeId, and Unix chargedAt.
Manual subscriptions and schedules
Declare support via the Update Capabilities API (companyId or locationId, OAuth scope required). Sparkforge then allows admin-created subscription schedules.
At runtime, Sparkforge POSTs type: "create_subscription" with locationId, contactId, paymentMethodId, subscriptionId, transactionId, startDate, currency, amount, recurringAmount, and full productDetails. Return a required subscriptionSnapshot (status: scheduled, trialing, active, expired, canceled, unpaid, incomplete) and an optional transaction for any initial charge.
Cancel subscription
For type: "cancel_subscription", return { "status": "canceled" }.
Step 6 — Refunds
Sparkforge sends type: "refund" with amount, transactionId, chargeId, and apiKey. Refunds can be partial; multiple refunds are allowed up to the original charge total. Respond with success, id, amount, currency, and a message.
Step 7 — Webhooks back to Sparkforge
POST JSON to https://backend.sparkforgehq.com/payments/custom-provider/webhook.
Supported events
subscription.trialingsubscription.activesubscription.updatedsubscription.chargedpayment.captured
Each payload includes event, chargeId, ghlSubscriptionId, ghlTransactionId, marketplaceAppId, locationId, apiKey, plus subscriptionSnapshot and chargeSnapshot where relevant.
Go-live checklist
- Marketplace app created with correct scopes and all four config sections filled.
- OAuth redirect collects code; backend exchanges for token; install/uninstall webhooks handled.
- Create Public Provider Config called on install with correct queryUrl and paymentsUrl.
- Test and live config saved via Connect Config; provider set as default.
- Iframe emits
custom_provider_ready, handlespayment_initiate_props, emits success/error/close. - Backend handles
verifywith success / failed / pending semantics. - Saved methods supported via
addCardOnFileSupported,setup_initiate_props,list_payment_methods,charge_payment. - Subscription schedules enabled via Update Capabilities;
create_subscriptionandcancel_subscriptionhandled. - Refunds handled.
- Webhooks emitted for all relevant events.
- End-to-end flows passed in test mode and re-run in live before launch.
Frequently Asked Questions
What languages can I use to build the integration?
My subscription stays Incomplete even after I send a subscription.active webhook. Why?
When do I send subscription.trialing vs subscription.active vs subscription.charged?
Who runs the recurring billing cycles?
Why don’t custom-provider saved cards show up under Contact → Payments → Manage Cards?
Can one integration handle multiple gateways?
Was this article helpful?
Thanks for the feedback.