---
updatedAt: 2026-09-03T22:10:29.000Z
agentTools:
  projectIndex: https://docs.joinforage.app/llms.txt
---

# Quickstart

Use the prebuilt Forage Hosted UI to collect a customer's EBT Card PIN and process EBT payments in your website and app.

In this guide, you'll learn how to:

1. Create a Forage `PaymentMethod`
2. Use the Forage Hosted UI to check a `PaymentMethod`'s balance
3. Use the Forage Hosted UI to process a payment

## Prerequisites

* Sign up for a Forage account ([contact us](https://www.joinforage.com/contact) if you don't yet have one)
* [Register an app](https://docs.joinforage.app/docs/register-an-app)
* [Create an authentication token](https://docs.joinforage.app/reference/create-authentication-token)

## Step 1: Create a Forage `PaymentMethod`

### 1. **Send a server-side `POST` to `/payment_methods/`.**

After a customer enters their EBT Card number in your app, send a server-side `POST` to [`/payment_methods/`](https://docs.joinforage.app/reference/create-payment-method) to tokenize the card, passing an EBT Card-specific request body. This request creates a Forage `PaymentMethod`, a representation of the card in Forage's database.

<Tabs>
  <Tab title="cURL">

```curl
curl --request POST \
     --url https://api.sandbox.joinforage.app/api/payment_methods/ \
     --header 'Authorization: Bearer <session-token>' \
     --header 'Merchant-Account: <merchant-id>' \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --data '
{
  "card": {
    "number": "<card-number>"
  },
  "type": "ebt",
  "reusable": true,
  "customer_id": "cus_1234567890"
}
'
```

  </Tab>

  <Tab title="Python">

```python
import requests

response = requests.post(
    "https://api.sandbox.joinforage.app/api/payment_methods/",
    headers={
        "Authorization": "Bearer <session-token>",
        "Merchant-Account": "<merchant-id>",
        "Accept": "application/json",
        "Content-Type": "application/json",
    },
    json={
        "card": {"number": "<card-number>"},
        "type": "ebt",
        "reusable": True,
        "customer_id": "cus_1234567890",
    },
)
print(response.json())
```

  </Tab>

  <Tab title="Java">

```java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

HttpClient client = HttpClient.newHttpClient();

String json = """
    {
      "card": {"number": "<card-number>"},
      "type": "ebt",
      "reusable": true,
      "customer_id": "cus_1234567890"
    }
    """;

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.sandbox.joinforage.app/api/payment_methods/"))
    .header("Authorization", "Bearer <session-token>")
    .header("Merchant-Account", "<merchant-id>")
    .header("Accept", "application/json")
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(json))
    .build();

HttpResponse<String> response = client.send(
    request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
```

  </Tab>

  <Tab title="Node">

```javascript Node
const response = await fetch(
  "https://api.sandbox.joinforage.app/api/payment_methods/",
  {
    method: "POST",
    headers: {
      Authorization: "Bearer <session-token>",
      "Merchant-Account": "<merchant-id>",
      Accept: "application/json",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      card: { number: "<card-number>" },
      type: "ebt",
      reusable: true,
      customer_id: "cus_1234567890",
    }),
  },
);
const data = await response.json();
console.log(data);
```

  </Tab>

  <Tab title="Ruby">

```ruby
require 'net/http'
require 'json'

uri = URI("https://api.sandbox.joinforage.app/api/payment_methods/")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

request = Net::HTTP::Post.new(uri.path)
request["Authorization"] = "Bearer <session-token>"
request["Merchant-Account"] = "<merchant-id>"
request["Accept"] = "application/json"
request["Content-Type"] = "application/json"
request.body = {
  card: { number: "<card-number>" },
  type: "ebt",
  reusable: true,
  customer_id: "cus_1234567890"
}.to_json

response = http.request(request)
puts JSON.parse(response.body)
```

  </Tab>

</Tabs>

*Example response:*

```json
{
  "ref": "2aa1583cdb",
  "type": "ebt",
  "reusable": false,
  "card": {
    "last_4": "9999",
    "created": "2024-02-06T12:59:43.147586-08:00",
    "token": "xxx,yyy",
    "state": null,
    "fingerprint": "470dda97b63f016a962de150cf53ad72a93aaea4c2a59de2541e0994f48e02ef",
    "fingerprint_v2": "a2f1b4e6c7d9380b59fa7c2e98ad0e316be9fcde1e2a4c3d56b17c6e0198a2fb"
  },
  "balance": null
}
```

### 2. **Store the `ref` that Forage returns in the response.**

You'll need the `ref` for the `PaymentMethod` to [check the card's balance](#step-2-use-the-forage-hosted-ui-to-check-a-paymentmethods-balance) and [charge payments](#step-3-use-the-forage-hosted-ui-to-process-a-payment) to it.

<Tabs>
  <Tab title="Node">

```javascript
// replace with your database setup
const database = new Map();

// middleware to parse the request body
app.use(bodyParser.json());

app.post("/api/add_forage_payment_method", (req, res) => {
  try {
    const { ref } = req.body;

    // replace with your database logic
    database.set(customerId, ref);

    res.json({
      success: true,
      message: "Forage PaymentMethod added successfully",
    });
  } catch (error) {
    res.status(500).json({ success: false, error: "Something went wrong" });
  }
});
```

  </Tab>

  <Tab title="Ruby">

```ruby
# app.rb
require 'sinatra'
require 'net/http'
require 'json'

class ApiController < Sinatra::Base
  post '/add_forage_payment_method' do
    begin
      ref = params[:ref]

      # Replace the following logic with your ORM model or database setup
      forage_payment_method = ForagePaymentMethod.new(customer_id: current_user.id, ref: ref)

      if forage_payment_method.save
        content_type :json
        { success: true, message: 'Forage PaymentMethod added successfully' }.to_json
      else
        status 500
        { success: false, error: 'Something went wrong' }.to_json
      end
    rescue StandardError => e
      status 500
      { success: false, error: 'Something went wrong' }.to_json
    end
  end
end
```

  </Tab>

</Tabs>

> 📘 PaymentMethod Creation Diagram
>
> For a visual representation of how to create a Forage `PaymentMethod` as part of a Custom integration, check out the [diagram](https://docs.joinforage.app/docs/custom#tokenize-an-ebt-pan-card-number).

## Step 2: Use the Forage Hosted UI to check a `PaymentMethod`'s balance

> ⚠️ Balance Inquiry Restriction
>
> Customers checking out as a guest are not permitted to perform balance checks by FNA regulation. Ensure you pass a `cusomter_id` when creating a balance session.

### 1. **Send a server-side `POST` to [`/balance_sessions/`](https://docs.joinforage.app/reference/create-custom-balance-check-session).**

Pass the `ref` for the `PaymentMethod` as the `payment_method` body parameter, along with two params that instruct Forage where to direct the customer after they complete the Hosted UI flow:

* `success_redirect_url`: The URL that Forage redirects your customer to if the balance inquiry is successful
* `cancel_redirect_url`: The URL that Forage redirects your customer to if the balance inquiry is canceled

<Tabs>
  <Tab title="cURL">

```curl
curl --request POST \
     --url https://api.sandbox.joinforage.app/api/balance_sessions/ \
     --header 'Authorization: Bearer <token>' \
     --header 'Idempotency-Key: <idempotency-key>' \
     --header 'Merchant-Account: <merchant-id>' \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --data '
{
  "payment_method": "485dffb5b0",
  "success_redirect_url": "https://www.your-app.com/status=SUCCEEDED",
  "cancel_redirect_url": "https://www.your-app.com/status=CANCELED"
}
'
```

  </Tab>

  <Tab title="Python">

```python
import requests

response = requests.post(
    "https://api.sandbox.joinforage.app/api/balance_sessions/",
    headers={
        "Authorization": "Bearer <token>",
        "Idempotency-Key": "<idempotency-key>",
        "Merchant-Account": "<merchant-id>",
        "Accept": "application/json",
        "Content-Type": "application/json",
    },
    json={
        "payment_method": "485dffb5b0",
        "success_redirect_url": "https://www.your-app.com/status=SUCCEEDED",
        "cancel_redirect_url": "https://www.your-app.com/status=CANCELED",
    },
)
print(response.json())
```

  </Tab>

  <Tab title="Java">

```java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

HttpClient client = HttpClient.newHttpClient();

String json = """
    {
      "payment_method": "485dffb5b0",
      "success_redirect_url": "https://www.your-app.com/status=SUCCEEDED",
      "cancel_redirect_url": "https://www.your-app.com/status=CANCELED"
    }
    """;

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.sandbox.joinforage.app/api/balance_sessions/"))
    .header("Authorization", "Bearer <token>")
    .header("Idempotency-Key", "<idempotency-key>")
    .header("Merchant-Account", "<merchant-id>")
    .header("Accept", "application/json")
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(json))
    .build();

HttpResponse<String> response = client.send(
    request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
```

  </Tab>

  <Tab title="Node">

```javascript Node
const response = await fetch(
  "https://api.sandbox.joinforage.app/api/balance_sessions/",
  {
    method: "POST",
    headers: {
      Authorization: "Bearer <token>",
      "Idempotency-Key": "<idempotency-key>",
      "Merchant-Account": "<merchant-id>",
      Accept: "application/json",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      payment_method: "485dffb5b0",
      success_redirect_url: "https://www.your-app.com/status=SUCCEEDED",
      cancel_redirect_url: "https://www.your-app.com/status=CANCELED",
    }),
  },
);
const data = await response.json();
console.log(data);
```

  </Tab>

  <Tab title="Ruby">

```ruby
require 'net/http'
require 'json'

uri = URI("https://api.sandbox.joinforage.app/api/balance_sessions/")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

request = Net::HTTP::Post.new(uri.path)
request["Authorization"] = "Bearer <token>"
request["Idempotency-Key"] = "<idempotency-key>"
request["Merchant-Account"] = "<merchant-id>"
request["Accept"] = "application/json"
request["Content-Type"] = "application/json"
request.body = {
  payment_method: "485dffb5b0",
  success_redirect_url: "https://www.your-app.com/status=SUCCEEDED",
  cancel_redirect_url: "https://www.your-app.com/status=CANCELED"
}.to_json

response = http.request(request)
puts JSON.parse(response.body)
```

  </Tab>

</Tabs>

*Example response:*

```json
{
  "payment_method": "485dffb5b0",
  "success_redirect_url": "https://www.your-app.com/status=SUCCEEDED",
  "cancel_redirect_url": "https://www.your-app.com/status=CANCELED",
  "ref": "93410bcaff",
  "redirect_url": "https://checkout.sandbox.joinforage.app/balance?session=93410bcaff&merchant=1234567"
}
```

The Forage response includes a `redirect_url` that launches the front-end, customer-facing Forage Hosted UI that prompts for a PIN to check the balance of a card. You need this value in the next step.

### 2. **Redirect the customer on the front-end to the `redirect_url` returned from the POST to `/balance_sessions/`.**

```typescript
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { createBalanceSession } from 'my-api';

const MyComponent = () => {
  const navigate = useNavigate();

  const redirectToBalanceSession = () => {
    const { redirect_url } = createBalanceSession();
    navigate(redirect_url);
  };

  return (
    <div>
      <p>Your component content here</p>
      <button onClick={redirectToBalanceSession}>Redirect to Balance URL</button>
    </div>
  );
};

export default MyComponent;
```

The customer follows the Forage Hosted UI prompts to input their card PIN to check its balance. If the check is successful, then Forage redirects the customer to the `success_redirect_url`. If unsuccessful, as in the case of a canceled balance check, Forage redirects them to the `cancel_redirect_url`.

#### How to override URL redirects on mobile

If your application deploys as a mobile app, then you will need to catch and override the URL redirects to keep the customer experience within the app. While you might have your own solution already, consider our recommendations:

<Tabs>
  <Tab title="iOS">

Use the [navigation delegate](https://developer.apple.com/documentation/webkit/wkwebview/1414971-navigationdelegate) property of WKWebView. When the WebView begins to redirect, inspect the URL to determine if it is either the `success_redirect_url` or the `cancel_redirect_url`. In either case, close the WebView and proceed through each corresponding flow. Check out this [discussion on implementation details](https://stackoverflow.com/questions/45604336/capture-redirect-url-in-wkwebview-in-ios) for more information.

```swift
import UIKit
import WebKit

class YourViewController: UIViewController, WKNavigationDelegate {

    var webView: WKWebView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // Initialize your WKWebView
        webView = WKWebView(frame: view.frame)
        webView.navigationDelegate = self

        // Load the initial URL
        if let url = URL(string: "https://checkout.sandbox.joinforage.app/balance?session=93410bcaff&merchant=1234567") {
            let request = URLRequest(url: url)
            webView.load(request)
        }

        view.addSubview(webView)
    }

    // Implement the WKNavigationDelegate method to handle navigation events
    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {

        // Inspect the URL during redirection
        if let url = navigationAction.request.url {
            let urlString = url.absoluteString

            // Check if the URL matches the success or cancel redirect URLs
            if urlString == "https://www.your-app.com/status=SUCCEEDED" {
                // Close the WebView and proceed with success flow
            } else if urlString == "https://www.your-app.com/status=CANCELED" {
                // Close the WebView and proceed with cancel flow
            } else {
                // Close with error handling
            }
        } else {
            // Close with error handling
        }

      decisionHandler(.cancel)
      dismiss(animated: true, completion: nil)
    }
}
```

  </Tab>
  <Tab title="Android">

We recommend using the [`shouldOverrideUrlLoading` function](https://developer.android.com/develop/ui/views/layout/webapps/webview#HandlingNavigation). Follow the same decision tree described above. More information on implementation can be found [this Stack Overflow discussion](https://stackoverflow.com/questions/19831645/android-catch-webview-redirection-url).

```kotlin
import android.os.Bundle
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.appcompat.app.AppCompatActivity

class YourWebViewActivity : AppCompatActivity() {

    private lateinit var webView: WebView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(<layout_resource_id>) // Replace with the actual layout resource ID

        webView = findViewById(<webview_id>) // Replace with the actual WebView ID in your layout
        webView.webViewClient = MyWebViewClient()

        // Load the initial URL
        webView.loadUrl("https://checkout.sandbox.joinforage.app/balance?session=93410bcaff&merchant=1234567")
    }

    private inner class MyWebViewClient : WebViewClient() {
        override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
            val url = request.url.toString()

            if (url == "https://www.your-app.com/?status=SUCCEEDED") {
                handleSuccessRedirect()

            } else if (url == "https://www.your-app.com/status=CANCELED"){
                handleCancelRedirect()
            } else {
              // Handle error case
            }
          return false // Cancel the navigation
        }
    }

    private fun handleSuccessRedirect() {
        // Your success handling logic goes here

        // Finish the activity or navigate back, etc.
        finish()
    }

  private fun handleCancelRedirect() {
    // Your cancel handling logic goes here

    // Finish the activity or navigate back, etc.
    finish()
  }
}
```

  </Tab>
</Tabs>

### 3. **Send a server-side `GET` to [`/payment_methods/{payment_method_ref}/`](https://docs.joinforage.app/reference/get-payment-method) to retrieve the customer's balance.**

The Forage `PaymentMethod` object stores the `balance` of the card. Pass the `ref` for the `PaymentMethod` in the body of the request:

<Tabs>
  <Tab title="cURL">

```curl
curl --request GET \
     --url https://api.sandbox.joinforage.app/api/payment_methods/485dffb5b0/ \
     --header 'Authorization: Bearer <session-token>' \
     --header 'Merchant-Account: <merchant-id>' \
     --header 'accept: application/json' \
```

  </Tab>

  <Tab title="Python">

```python
import requests

response = requests.get(
    "https://api.sandbox.joinforage.app/api/payment_methods/485dffb5b0/",
    headers={
        "Authorization": "Bearer <session-token>",
        "Merchant-Account": "<merchant-id>",
        "Accept": "application/json",
    },
)
print(response.json())
```

  </Tab>

  <Tab title="Java">

```java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

HttpClient client = HttpClient.newHttpClient();

HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.sandbox.joinforage.app/api/payment_methods/485dffb5b0/"))
.header("Authorization", "Bearer <session-token>")
.header("Merchant-Account", "<merchant-id>")
.header("Accept", "application/json")
.GET()
.build();

HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());

```

  </Tab>

  <Tab title="Node">

```javascript Node
const response = await fetch(
  "https://api.sandbox.joinforage.app/api/payment_methods/485dffb5b0/",
  {
    headers: {
      Authorization: "Bearer <session-token>",
      "Merchant-Account": "<merchant-id>",
      Accept: "application/json",
    },
  },
);
const data = await response.json();
console.log(data);
```

  </Tab>
  <Tab title="Ruby">

```ruby
require 'net/http'
require 'json'

uri = URI("https://api.sandbox.joinforage.app/api/payment_methods/485dffb5b0/")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

request = Net::HTTP::Get.new(uri.path)
request["Authorization"] = "Bearer <session-token>"
request["Merchant-Account"] = "<merchant-id>"
request["Accept"] = "application/json"

response = http.request(request)
puts JSON.parse(response.body)
```

  </Tab>
</Tabs>

*Example response:*

```json
{
  "ref": "485dffb5b0",
  "type": "ebt",
  "reusable": true,
  "balance": {
    "snap": "25.99",
    "non_snap": "10.99",
    "updated": "2021-06-16T00:11:50.000000Z-07:00"
  },
  "card": {
    "last_4": "3456",
    "created": "2021-06-16T00:11:50.000000Z-07:00",
    "token": "tok_sandbox_12345678901234567890",
    "state": "CA"
  }
}
```

Retrieve the `balance` returned in the response and display it to the customer on the front-end.

> 📘 Card Balance Check Diagram
>
> For a visual representation of how to check the balance of a card as part of a Custom integration, check out the [diagram](https://docs.joinforage.app/docs/custom#check-the-balance-of-an-ebt-account).

## Step 3: Use the Forage Hosted UI to process a payment

### 1. **Send a server-side `POST` to `/capture_sessions/`.**

This request creates a Forage `Order` , a representation in Forage's database of the customer's checkout interaction.

Here's a sample request:

<Tabs>

  <Tab title="cURL">

```curl
curl --request POST \
     --url https://api.sandbox.joinforage.app/api/capture_sessions/ \
     --header 'Authorization: Bearer <authentication_token>' \
     --header 'Idempotency-Key: <idempotency-key>' \
     --header 'Merchant-Account: <merchant-id>' \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --data '

{
    "success_redirect_url" : "https://www.joinforage.com/status=SUCCEEDED",
    "cancel_redirect_url" : "https://www.joinforage.com/status=CANCELED",
    "delivery_address": {
        "city": "San Francisco",
        "country": "US",
        "line1": "1857 Market St.",
        "state": "CA",
        "zipcode": "94117"
    },
    "is_delivery" : false,
    "supported_benefits": ["snap", "ebt_cash"],
    "payment_details": {
        "snap_payment": {
           "amount": 15.00,
           "metadata": {},
           "payment_method": "fsdf45345",
           "description": "A SNAP payment",
           "funding_type": "ebt_snap"
        },
        "ebt_cash_payment": {
            "amount": 4.90,
            "metadata": {},
            "payment_method": "fsdf45345",
            "description": "An EBT Cash payment",
            "funding_type": "ebt_cash"
         }
    },
    "product_list": [
      {
        "name": "Granny Smith Apple (1 pound)",
        "upc": "860",
        "unit_price": 7.50,
        "quantity": 2,
        "eligibility": "snap"
      },
      {
        "name": "Take and Bake Lasagna",
        "upc": "861",
        "unit_price": 4.90,
        "quantity": 1,
        "eligibility": "ebt_cash"
      }
    ]
}
'
```

  </Tab>

  <Tab title="Python">

```python
import requests

response = requests.post(
    "https://api.sandbox.joinforage.app/api/capture_sessions/",
    headers={
        "Authorization": "Bearer <authentication_token>",
        "Idempotency-Key": "<idempotency-key>",
        "Merchant-Account": "<merchant-id>",
        "Accept": "application/json",
        "Content-Type": "application/json",
    },
    json={
        "success_redirect_url": "https://www.joinforage.com/status=SUCCEEDED",
        "cancel_redirect_url": "https://www.joinforage.com/status=CANCELED",
        "delivery_address": {
            "city": "San Francisco",
            "country": "US",
            "line1": "1857 Market St.",
            "state": "CA",
            "zipcode": "94117",
        },
        "is_delivery": False,
        "supported_benefits": ["snap", "ebt_cash"],
        "payment_details": {
            "snap_payment": {
                "amount": 15.00,
                "metadata": {},
                "payment_method": "fsdf45345",
                "description": "A SNAP payment",
                "funding_type": "ebt_snap",
            },
            "ebt_cash_payment": {
                "amount": 4.90,
                "metadata": {},
                "payment_method": "fsdf45345",
                "description": "An EBT Cash payment",
                "funding_type": "ebt_cash",
            },
        },
        "product_list": [
            {
                "name": "Granny Smith Apple (1 pound)",
                "upc": "860",
                "unit_price": 7.50,
                "quantity": 2,
                "eligibility": "snap"
            },
            {
                "name": "Take and Bake Lasagna",
                "upc": "861",
                "unit_price": 4.90,
                "quantity": 1,
                "eligibility": "ebt_cash"
            }
        ]
    },
)
print(response.json())
```

  </Tab>

  <Tab title="Java">

```java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

HttpClient client = HttpClient.newHttpClient();

String json = """
    {
      "success_redirect_url": "https://www.joinforage.com/status=SUCCEEDED",
      "cancel_redirect_url": "https://www.joinforage.com/status=CANCELED",
      "delivery_address": {
        "city": "San Francisco",
        "country": "US",
        "line1": "1857 Market St.",
        "state": "CA",
        "zipcode": "94117"
      },
      "is_delivery": false,
      "supported_benefits": ["snap", "ebt_cash"],
      "payment_details": {
        "snap_payment": {
          "amount": 15.00,
          "metadata": {},
          "payment_method": "fsdf45345",
          "description": "A SNAP payment",
          "funding_type": "ebt_snap"
        },
        "ebt_cash_payment": {
          "amount": 4.90,
          "metadata": {},
          "payment_method": "fsdf45345",
          "description": "An EBT Cash payment",
          "funding_type": "ebt_cash"
        }
      }
      "product_list": [
        {
          "name": "Granny Smith Apple (1 pound)",
          "upc": "860",
          "unit_price": 7.50,
          "quantity": 2,
          "eligibility": "snap"
        },
        {
          "name": "Take and Bake Lasagna",
          "upc": "861",
          "unit_price": 4.90,
          "quantity": 1,
          "eligibility": "ebt_cash"
        }
      ]
    }
    """;

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.sandbox.joinforage.app/api/capture_sessions/"))
    .header("Authorization", "Bearer <authentication_token>")
    .header("Idempotency-Key", "<idempotency-key>")
    .header("Merchant-Account", "<merchant-id>")
    .header("Accept", "application/json")
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(json))
    .build();

HttpResponse<String> response = client.send(
    request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
```

  </Tab>
  <Tab title="Node">

```javascript Node
const response = await fetch(
  "https://api.sandbox.joinforage.app/api/capture_sessions/",
  {
    method: "POST",
    headers: {
      Authorization: "Bearer <authentication_token>",
      "Idempotency-Key": "<idempotency-key>",
      "Merchant-Account": "<merchant-id>",
      Accept: "application/json",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      success_redirect_url: "https://www.joinforage.com/status=SUCCEEDED",
      cancel_redirect_url: "https://www.joinforage.com/status=CANCELED",
      delivery_address: {
        city: "San Francisco",
        country: "US",
        line1: "1857 Market St.",
        state: "CA",
        zipcode: "94117",
      },
      is_delivery: false,
      supported_benefits: ["snap", "ebt_cash"],
      payment_details: {
        snap_payment: {
          amount: 15.0,
          metadata: {},
          payment_method: "fsdf45345",
          description: "A SNAP payment",
          funding_type: "ebt_snap",
        },
        ebt_cash_payment: {
          amount: 4.9,
          metadata: {},
          payment_method: "fsdf45345",
          description: "An EBT Cash payment",
          funding_type: "ebt_cash",
        },
      },
      product_list: [
        {
          name: "Granny Smith Apple (1 pound)",
          upc: "860",
          unit_price: 7.5,
          quantity: 2,
          eligibility: "snap",
        },
        {
          name: "Take and Bake Lasagna",
          upc: "861",
          unit_price: 4.9,
          quantity: 1,
          eligibility: "ebt_cash",
        },
      ],
    }),
  },
);
const data = await response.json();
console.log(data);
```

  </Tab>

<Tab title="Ruby">

```ruby
require 'net/http'
require 'json'

uri = URI("https://api.sandbox.joinforage.app/api/capture_sessions/")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

request = Net::HTTP::Post.new(uri.path)
request["Authorization"] = "Bearer <authentication_token>"
request["Idempotency-Key"] = "<idempotency-key>"
request["Merchant-Account"] = "<merchant-id>"
request["Accept"] = "application/json"
request["Content-Type"] = "application/json"
request.body = {
  success_redirect_url: "https://www.joinforage.com/status=SUCCEEDED",
  cancel_redirect_url: "https://www.joinforage.com/status=CANCELED",
  delivery_address: {
    city: "San Francisco",
    country: "US",
    line1: "1857 Market St.",
    state: "CA",
    zipcode: "94117"
  },
  is_delivery: false,
  supported_benefits: ["snap", "ebt_cash"],
  payment_details: {
    snap_payment: {
      amount: 15.00,
      metadata: {},
      payment_method: "fsdf45345",
      description: "A SNAP payment",
      funding_type: "ebt_snap"
    },
    ebt_cash_payment: {
      amount: 4.90,
      metadata: {},
      payment_method: "fsdf45345",
      description: "An EBT Cash payment",
      funding_type: "ebt_cash"
    }
  }
  product_list: [
    {
      name: "Granny Smith Apple (1 pound)",
      upc: "860",
      unit_price: 7.50,
      quantity: 2,
      eligibility: "snap"
    },
    {
      name: "Take and Bake Lasagna",
      upc: "861",
      unit_price: 4.90,
      quantity: 1,
      eligibility: "ebt_cash"
    }
  ]
}.to_json

response = http.request(request)
puts JSON.parse(response.body)
```

  </Tab>

</Tabs>

*Example response:*

```json
{
  "status": "draft",
  "delivery_address": {
    "city": "San Francisco",
    "country": "US",
    "line1": "1857 Market St.",
    "state": "CA",
    "zipcode": "94117"
  },
  "is_delivery": false,
  "payment_details": {
    "snap_payment": {
      "ref": "b29dj92e4g",
      "amount": 15.0,
      "metadata": {},
      "payment_method": "fsdf45345",
      "description": "A SNAP payment",
      "funding_type": "ebt_snap"
    },
    "ebt_cash_payment": {
      "ref": "c4gdj92e4g",
      "amount": 4.9,
      "metadata": {},
      "payment_method": "fsdf45345",
      "description": "An EBT Cash payment",
      "funding_type": "ebt_cash"
    }
  },
  "supported_benefits": ["snap", "ebt_cash"],
  "ref": "b873fe62dc",
  "success_date": null,
  "receipt": null,
  "customer_id": null,
  "is_commercial_shipping": null,
  "success_redirect_url": "https://www.your-app.com/?status=SUCCEEDED",
  "cancel_redirect_url": "https://www.your-app.com/?status=CANCELED",
  "redirect_url": "https://checkout.sandbox.joinforage.app/payment?order=b873fe62dc&merchant=9000055"
}
```

The response includes a `redirect_url` that launches the front-end, customer-facing Forage Hosted UI that prompts for a PIN to capture a payment.

### 2. **Redirect the customer on the front-end to the `redirect_url`.**

```typescript
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { createCaptureSession } from 'my-api';

const MyComponent = () => {
  const navigate = useNavigate();

  const redirectToCaptureSession = () => {
    const { redirect_url } = createCaptureSession();
    navigate(redirect_url);
  };

  return (
    <div>
      <p>Your component content here</p>
      <button onClick={redirectToCaptureSession}>Redirect to Capture URL</button>
    </div>
  );
};

export default MyComponent;
```

The customer follows the Forage Hosted UI prompts to input their PIN to capture a payment. If the payment is successful, then Forage redirects them to the `success_redirect_url`. If unsuccessful, as in the case of a canceled payment, Forage redirects them to the `cancel_redirect_url`.

If you're building a mobile integration, then you need to follow the steps to [intercept the redirects to keep the experience in-app](#how-to-override-url-redirects-on-mobile).

### 3. **Check the `Order` status.**

To check the status of an entire order, send a `GET` to [`/orders/{order_ref}/`](https://docs.joinforage.app/reference/get-order):

<Tabs>

  <Tab title="cURL">

```curl
curl --request GET \
     --url https://api.sandbox.joinforage.app/api/orders/dee3c3045e/ \
     --header 'Authorization: Bearer <authentication_token>' \
     --header 'Idempotency-Key: <idempotency-key>' \
     --header 'Merchant-Account: <merchant-id>' \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
```

  </Tab>
  <Tab title="Python">

```python
import requests

response = requests.get(
    "https://api.sandbox.joinforage.app/api/orders/dee3c3045e/",
    headers={
        "Authorization": "Bearer <authentication_token>",
        "Idempotency-Key": "<idempotency-key>",
        "Merchant-Account": "<merchant-id>",
        "Accept": "application/json",
    },
)
print(response.json())
```

  </Tab>

  <Tab title="Java">

```java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

HttpClient client = HttpClient.newHttpClient();

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.sandbox.joinforage.app/api/orders/dee3c3045e/"))
    .header("Authorization", "Bearer <authentication_token>")
    .header("Idempotency-Key", "<idempotency-key>")
    .header("Merchant-Account", "<merchant-id>")
    .header("Accept", "application/json")
    .GET()
    .build();

HttpResponse<String> response = client.send(
    request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
```

  </Tab>

  <Tab title="Node">

```javascript Node
const response = await fetch(
  "https://api.sandbox.joinforage.app/api/orders/dee3c3045e/",
  {
    headers: {
      Authorization: "Bearer <authentication_token>",
      "Idempotency-Key": "<idempotency-key>",
      "Merchant-Account": "<merchant-id>",
      Accept: "application/json",
    },
  },
);
const data = await response.json();
console.log(data);
```

  </Tab>

  <Tab title="Ruby">

```ruby
require 'net/http'
require 'json'

uri = URI("https://api.sandbox.joinforage.app/api/orders/dee3c3045e/")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

request = Net::HTTP::Get.new(uri.path)
request["Authorization"] = "Bearer <authentication_token>"
request["Idempotency-Key"] = "<idempotency-key>"
request["Merchant-Account"] = "<merchant-id>"
request["Accept"] = "application/json"

response = http.request(request)
puts JSON.parse(response.body)
```

  </Tab>
</Tabs>

*Example response:*

```json
{
  "ref": "93c7dad6b5",
  "funding_type": "ebt_snap",
  "amount": "20.00",
  "description": "EBT SNAP payment",
  "metadata": {},
  "payment_method": "485dffb5b0",
  "created": "2023-10-05T17:38:26.698516-07:00",
  "updated": "2023-10-05T17:38:26.698540-07:00",
  "status": "requires_confirmation",
  "last_processing_error": null,
  "success_date": null,
  "tpp_lookup_id": null,
  "customer_id": null,
  "external_order_id": null,
  "refunds": [],
  "order": "dee3c3045e"
}
```

Inspect the `status` field in the response body. If the status is `succeeded`, then all `OrderPayments` have processed successfully. If the status is `failed`, then check the `Order.receipt.message` field for details about the error.

You can also inspect the outcome of a single `OrderPayment` via a `GET` to [`/orders/{order_ref}/payments/{payment_ref}/`](https://docs.joinforage.app/reference/get-an-order-payment).

> ⚠️ Retry After Failed Transaction
>
> If a transaction fails, repeat the steps above. Create a new `Order` via another `POST` to `/capture_sessions/`, which will create a new corresponding `OrderPayment`.

> 📘 Payment Processing Diagram
>
> For a visual representation of how to process a payment as part of a Hosted integration, check out the [diagram](https://docs.joinforage.app/docs/hosted#capture-a-payment).

# Sibling pages

* [Introduction](https://docs.joinforage.app/docs/hosted.md)
* [Reuse Payment Methods](https://docs.joinforage.app/docs/hosted-reuse-payment-methods.md)
* [Errors](https://docs.joinforage.app/docs/hosted-errors.md)