iOS HSA/FSA Quickstart
Use the Forage iOS SDK to accept HSA/FSA payments in your iOS app.
Looking for the Forage mobile app?
Get instant EBT balance checks and access to exclusive grocery deals—download Forage on iOS or Android and start saving today.
Get up and running with Forage’s iOS SDK to accept HSA/FSA and EBT payments in your iOS app. This guide walks you through the installation, setup, styling, and execution of key payment operations.
In this guide, you’ll learn how to:
- Set up a Forage iOS app
- Add and style a Forage Payment Sheet
- Perform payment operations
- Tokenize and store a payment method
- Authorize and Capture a payment
Prerequisites
Before we get started, make sure you have the following ready to go:
-
A Forage account
(Contact us if you don’t have one yet.) -
Installed tools:
- Xcode v14.1 or newer
- iOS SDK v13 or higher
- Swift v5.5 or higher
-
Required third-party libraries:
-
(Optional) CocoaPods
Use CocoaPods to install the SDK and manage dependencies.
For more information, see:
- Payments API reference docs
- Forage iOS SDK on GitHub, including a sample app and iOS SDK reference docs
Step 1: Set up a Forage iOS app
1.1 Add an endpoint to your server
A POST
to the Forage /session_token/
endpoint creates a session token.
Environment Variable Best Practices
These examples assume you’re using a
.env
file. Update them as needed, but never expose sensitive data, such as tokens, in client-side code.
// app.js
const axios = require('axios')
const express = require('express')
const bodyParser = require('body-parser')
require('dotenv').config();
const app = express()
const port = 5000
// Middleware to parse the request body
app.use(bodyParser.urlencoded({ extended: true }))
app.post('/api/session_token', async (req, res) => {
try {
const apiUrl = 'https://api.sandbox.joinforage.app/api/session_token/'
// Replace the below with your authentication token
const authenticationToken = process.env.AUTHENTICATION_TOKEN
// Replace the below with your Forage merchant ID
const merchantId = process.env.MERCHANT_ID
const headers = {
Accept: 'application/json',
Authorization: `Bearer ${authenticationToken}`,
'Merchant-Account': merchantId,
'Content-Type': 'application/x-www-form-urlencoded'
}
const response = await axios.post(apiUrl, null, { headers })
res.json(response.data)
} catch (error) {
res
.status(error.response ? error.response.status : 500)
.json({ error: 'Something went wrong' })
}
})
app.listen(port, () => {
console.log(`Server is running on http://localhost:${port}`)
})
# app.rb
require 'sinatra'
require 'net/http'
require 'json'
require 'dotenv/load'
class ApiController < Sinatra::Base
post '/session_token' do
begin
api_url = 'https://api.sandbox.joinforage.app/api/session_token/'
# Replace the below with your authentication token
authentication_token = ENV['AUTHENTICATION_TOKEN']
# Replace the below with your Forage merchant ID
merchant_id = ENV['MERCHANT_ID']
uri = URI(api_url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
headers = {
'Accept' => 'application/json',
'Authorization' => "Bearer #{authentication_token}",
'Merchant-Account' => merchant_id,
'Content-Type' => 'application/x-www-form-urlencoded',
}
response = http.post(uri.path, nil, headers)
if response.is_a?(Net::HTTPSuccess)
json_response = JSON.parse(response.body)
content_type :json
json_response.to_json
else
status response.code
{ error: 'Something went wrong' }.to_json
end
rescue StandardError => e
status e.respond_to?(:status) ? e.status : 500
{ error: 'Something went wrong' }.to_json
end
end
end
1.2 Install the iOS SDK.
You can either use CocoaPods or XCode’s built-in Swift Package Manager (SPM) to install forage-ios-sdk
.
CocoaPods
If you haven’t already, install the latest version of CocoaPods. Run the following command if you don’t have an existing Podfile
:
pod init
Add the below line to your Podfile
:
pod 'ForageSDK', '~> 4.4.4'
Run the installation command:
pod install
To update to the latest version of the SDK, run:
pod repo update
pod update ForageSDK
Xcode
Navigate to Add Packages...

Add the https://github.com/teamforage/forage-ios-sdk
dependency. Set the Dependency Rule
to Branch and type main
for the branch name. Finally, click Add Package
.

On the next screen, select the ForageSDK
package and click Add Package again
.

For more detailed instructions, consult the official Apple SPM guide.
1.3 Import the SDK
Add the following import to the top of every file that uses the library.
import ForageSDK
// your code here
1.4 Initialize the SDK
Set up a request from your front-end to your backend that asks the server to generate a session token.
In your app’s primary ViewController.swift
, call ForageSDK.setup
to initialize the SDK. Pass a ForageSDK.Config
object that specifies the merchantID
and the server-generated sessionToken
.
// ViewController.swift
ForageSDK.setup(
ForageSDK.Config(
merchantID: "d5685d56ed",
sessionToken: "sandbox_eyJ0eXAiOiJKV1Qi..."
)
)
Step 2: Add and style a Forage Payment Sheet
A Forage Payment Sheet is a secure, client-side entity that accepts and submits customer input for an HSA/FSA transaction.
The iOS SDK includes a ForagePaymentSheet
Element for collecting all of the required HSA/FSA card data, pictured below.

The ForagePaymentSheet element
2.1 Initialize the ForagePaymentSheet
To add a Payment Sheet, call the method to initialize the Element in a View.swift
for a specific payment operation. You can set styling properties after calling the method.
The below example initializes a ForagePaymentSheet
Element via a call to ForagePaymentSheet()
in a HSAFSAView.swift
and specifies some styling properties:
// HSAFSAView.swift
private let foragePaymentSheet: ForagePaymentSheet = {
let ps = ForagePaymentSheet()
// set paymentType on the sheet
ps.paymentType = .HSAFSA
// set default styles on payment sheet that will cascade to all fields
ps.borderWidth = 2.5
ps.borderColor = .black
ps.cornerRadius = 4
// Set TextField specific settings and accessibility settings
ps.cardHolderNameTextField.placeholder = "Card holder name"
ps.cardHolderNameTextField.accessibilityIdentifier = "tf_paymentsheet_cardholderName"
ps.cardHolderNameTextField.isAccessibilityElement = true
return ps
}()
For more information, see How to Style Forage iOS Elements.
2.2 Set up text input validation
Forage validates a Payment Sheet’s input as a customer types. To notify customers of input validation errors, you'll need to conform to the ForagePaymentSheetElementDelegate
protocol and listen for the Payment Sheet’s PaymentSheetObservableState
.
The sheetFocusDidChange
method is triggered when a text field in the Payment Sheet gains or loses focus.
foragePaymentSheet.delegate = self
// signature
public protocol ForagePaymentSheetElementDelegate {
func sheetTextFieldDidChange(_ state: PaymentSheetObservableState)
func sheetFocusDidChange(_ state: PaymentSheetObservableState)
}
// usage
extension HSAFSAView: ForagePaymentSheetElementDelegate {
func sheetFocusDidChange(_ state: any PaymentSheetObservableState) {
if state.currentFirstResponder != nil {} // Payment Sheet gained focus
else {} // Payment Sheet lost focus (blurred)
// view the currently focused field
print(state.currentFirstResponder?.name ?? "No focus")
}
func sheetTextFieldDidChange(_ state: any PaymentSheetObservableState) {
// you can view all payment sheet completion errors
print(state.completionErrors)
// you can highlight fields with errors
for var field in foragePaymentSheet.fields {
if !field.isValid && !field.isFirstResponder && field.isDirty {
field.borderColor = .red
} else {
field.borderColor = .black
}
}
}
}
The PaymentSheetObservableState
protocol defines properties reflecting the state of a Payment Sheet.
public protocol PaymentSheetObservableState {
/// isComplete is true when all inputs on the sheet are valid and the sheet ready to be submitted.
var isComplete: Bool { get }
var completionErrors: [String: any Error] { get }
var currentFirstResponder: (any ForagePaymentSheetField)? { get }
}
In the View.swift
for the payment operation, indicate the delegate in the primary render()
, as in the following example for the foragePaymentSheet
in the HSAFSAView.swift
:
// HSAFSAView.swift
public func render() {
foragePaymentSheet.delegate = self
setupView()
setupConstraints()
}
Then, specify the ForagePaymentSheetElementDelegate
as an extension
in the file. Set the state
to PaymentSheetObservableState
. Define functions to handle state changes in the extension as well.
The below example sets up an extension
for the ForagePaymentSheet
in the HSAFSAView.swift
:
// HSAFSAView.swift
extension HSAFSAView: ForagePaymentSheetElementDelegate {
func sheetFocusDidChange(_ state: PaymentSheetObservableState) {
updateState(state: state)
}
func sheetTextFieldDidChange(_ state: PaymentSheetObservableState) {
updateState(state: state)
}
}
2.3 Establish a ForagePaymentSheet
TextField as the First Responder
ForagePaymentSheet
TextField as the First ResponderFinally, in the Controller.swift
for the payment operation, call becomeFirstResponder()
to set focus on the Forage Element TextField.
The following example calls the method as part of the lifecycle methods in a HSAFSAViewController.swift
:
class HSAFSAViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// You can change which subfield becomes first responder
customView.foragePaymentSheet.cardHolderNameTextField.becomeFirstResponder()
}
}
Step 3: Perform payment operations
3.1 Tokenize and store a payment method
📘 How To Tokenize With a Payment Sheet
To tokenize an HSA/FSA Card number, use the ForagePaymentSheet
Element.
Building on the example HSAFSAView.swift
, call the ForageSDK.shared.tokenizeCreditDebitCard
method.
// HSAFSAView.swift
ForageSDK.shared.tokenizeCreditDebitCard(
foragePaymentSheet: foragePaymentSheet,
// NOTE: The following line is for testing purposes only and should not be used in production.
// Please replace this line with a hashed customer ID value.
customerID: UUID.init().uuidString,
reusable: true
) { result in
// Handle result and error here
switch result {
case .success(let paymentMethod):
// Handle the PaymentMethod here (paymentMethodIdentifier, card, type, etc.)
case .failure(let error):
if let forageError = error as? ForageError {
// handle forageError.code, forageError.httpStatusCode and forageError.message
}
}
}
3.2 Authorize and Capture a payment
Follow the Authorize and Capture an HSA/FSA payment guide from this point to complete the payment.
Updated about 18 hours ago