# Integrate WIC with Forage
Step-by-step instructions for each WIC integration phase — APL sync, balance inquiry, capture, and refunds.
> For conceptual background on how WIC differs from dollar-based payment methods, see [How WIC Payments Work](./wic-payments.md). For complete endpoint specs, see the [WIC API Reference](./wic-api-reference.md).
***
## Integration requirements
A complete Online WIC EBT implementation requires:
* Inventory management — WIC benefits may only be used for specific, approved items in defined quantities. Payment processors require a detailed item list when processing WIC transactions. Merchants must tag their inventory against the Approved Product List (APL) for each WIC agency to present eligible items at checkout.
* Payment method management — WIC EBT uses its own card numbers and IINs, which differ from those used for EBT SNAP. There are also more issuing WIC EBT agencies than SNAP EBT agencies.
* Payment processing that complies with the WIC Operating Rules. These rules define the steps required to process a WIC transaction successfully and specify the formats for various receipt types.
* Support for refunds, voids, and item substitutions.
**Integration effort:** Now that you've already integrated EBT SNAP with Forage, the payment method and payment processing build on your existing implementation. Only inventory management represents net-new integration work.
**What's the difference between your SNAP and WIC integrations?** Most of the work is already done. WIC reuses your existing EBT infrastructure; you need to modify the capture and refund endpoints to include basket-level data.
***
## Sync APL inventory data
WIC eligibility is determined at the UPC/PLU level (Universal Product Code / Price Lookup Code) and varies by agency. A product eligible in California may not be eligible in Texas. Forage maintains Authorized Product Lists (APLs) for all WIC agencies and exposes them via API so you can tag your inventory. The APL contains every UPC/PLU eligible for WIC purchase within an agency. APLs refresh daily, so you must sync this data with your product catalog daily.
**To sync APL data with your catalog:**
1. Call `GET /api/program_details/` to retrieve the list of WIC agencies and their card BIN/PAN data. The `agency_id` returned during card tokenization (via `card.agency_id`) tells you which agency's APL applies to a given customer. See [GET /api/program\_details/](./wic-api-reference.md#get-apiprogram_details--agency-and-card-details) for the response spec.
2. Call `GET /api/wic/categories/` to retrieve the WIC category and subcategory structure. Store this locally to map `category_code` and `subcategory_code` values to human-readable names. See [GET /api/wic/categories/](./wic-api-reference.md#get-apiwic-categories--wic-food-categories) for the response spec.
3. Call `GET /api/wic/apl/` to pull eligible items for each agency. Pass `since` to fetch only items updated since your last sync (supports up to 30 days of history). Pass `agency_id` to scope results to a specific agency. See [GET /api/wic/apl/](./wic-api-reference.md#get-apiwicapl--approved-product-list) for the full request and response spec.
4. Tag your product catalog with the `category_code`, `subcategory_code`, `unit_of_measure`, and `benefit_quantity` from the APL for each matching item.
Your catalog is ready to match against customer prescriptions at shopping time.
***
## Tokenize a WIC card
You've already tokenized EBT cards during your SNAP integration. WIC tokenization uses the same endpoint and flow. Before you can check a WIC cardholder's balance or capture a payment, you need to tokenize their card.
Call `POST /api/payment_methods/` with `type: "wic"`. The response includes `card.agency_id`, which identifies the WIC agency the card belongs to. Use it to scope APL lookups and benefit rules for the cardholder.
See [POST /api/payment\_methods/](./wic-api-reference.md#post-apipayment_methods--tokenize-a-wic-card) for the full request and response spec. For WIC test card numbers, see [test cards](https://docs.joinforage.app/docs/test-cards).
***
## Perform a balance inquiry
Before capturing a WIC payment, you must perform a balance inquiry.
Like EBT balance checks, WIC balance inquiry verifies the cardholder's PIN and creates a session that includes a redirect to a Forage-hosted PIN collection page. The key difference: WIC returns a prescription (a list of available benefits by category) instead of a dollar balance.
This step:
* Verifies the cardholder's PIN
* Returns their available benefits (the "prescription")
* Creates a session that allows subsequent transactions without re-entering the PIN
The diagram below shows the three-party flow (your app, the customer's browser, and Forage) and how the session `ref` carries through from PIN entry to confirmation and capture:
```mermaid
sequenceDiagram
participant App as Your App
participant Browser as Customer Browser
participant Forage
rect rgb(219, 234, 254)
note over App,Forage: Phase 1 — Balance Inquiry & PIN
App->>+Forage: POST /api/balance_sessions/
Forage-->>-App: { redirect_url, ref }
App->>Browser: Redirect to redirect_url
Browser->>+Forage: Customer opens Forage-hosted PIN page
note over Browser,Forage: Customer enters PIN here.
You never handle the PIN directly.
Forage-->>-Browser: Redirect → success_redirect_url?balance_session_ref={ref}
Browser->>App: Returns to your app (ref in query params)
App->>+Forage: GET /api/balance_sessions/{ref}/
Forage-->>-App: { prescription, pin_expires_at }
end
note over App,Forage: Session ref valid until pin_expires_at — pass it to all subsequent calls
rect rgb(224, 231, 255)
note over App,Forage: Phase 2 — Cart Confirmation (call on every cart change)
App->>+Forage: POST /confirm/ with cart items
Forage-->>-App: { covered_items, uncovered_items, benefit_usage }
App->>Browser: Show real-time benefit breakdown
end
rect rgb(209, 250, 229)
note over App,Forage: Phase 3 — Payment Capture (must complete before pin_expires_at)
App->>+Forage: POST /api/payments/ (create)
Forage-->>-App: { payment_ref }
App->>+Forage: POST /capture_payment with product_list
Forage-->>-App: { status, action_codes, receipt }
end
```
### Create a balance session
**To perform a balance inquiry:**
1. Call `POST /api/balance_sessions/` with the payment method `ref` and your `success_redirect_url` and `cancel_redirect_url`. See [POST /api/balance\_sessions/](./wic-api-reference.md#post--get-apibalance_sessions--balance-inquiry-endpoints) for the full request spec. The response includes a `redirect_url`.
2. Redirect the customer to the `redirect_url`. Forage's hosted page securely handles PIN capture (you never directly handle the PIN). After the customer enters their PIN:
* Forage verifies the PIN with the WIC processor
* Forage retrieves the customer's prescription (available benefits)
* The customer is redirected to your `success_redirect_url` with `?balance_session_ref={ref}` appended as a query parameter
* If the customer cancels or PIN entry fails, they are redirected to your `cancel_redirect_url` with the ref appended
> 📘 Query Parameters
>
> Forage appends `balance_session_ref` as a query parameter. If your URL already contains query parameters, Forage will append using `&` instead of `?`
3. Read the `balance_session_ref` from the query parameter on your redirect landing page, then call `GET /api/balance_sessions/{ref}/` to retrieve the prescription. See [GET /api/balance\_sessions/{ref}/](./wic-api-reference.md#post--get-apibalance_sessions--balance-inquiry-endpoints) for the full response spec.
Store the `ref` from the response and pass it to subsequent payment capture and order modification calls. This links to the verified PIN and avoids requiring the customer to re-enter their PIN.
The `ref` remains valid until `pin_expires_at`. During this window, you can repeatedly request WIC Confirmations, capture a payment, and modify the order, all without the customer re-entering their PIN. See [Balance Session Lifetime and Scope](./wic-api-reference.md#balance-session-lifetime-and-scope) for details.
***
## Build the WIC-eligible cart
This is a new requirement for WIC. Unlike EBT SNAP's dollar-based eligibility, WIC requires real-time item-level validation against the customer's prescription.
There are three parts to presenting WIC benefits during the shopping experience:
1. **Eligible item highlighting.** Use the customer's `prescription` from the Balance Session to match against your APL-tagged catalog. This lets you surface exactly which items the customer can purchase with their available benefits.
2. **Real-time cart feedback.** As the customer adds items to their cart, show a `WIC Confirmation`: a running breakdown of how their cart is consuming their prescription. This helps them avoid leaving benefits unused, since WIC benefits expire monthly (unlike SNAP).
3. **Checkout confirmation.** Before the customer completes payment, present the final `WIC Confirmation` so they can review and consent to how their WIC benefits will be applied. PIN entry is not required at this step.
### The WIC Confirmation
Use the WIC Confirmation endpoint on every cart change to give customers real-time feedback as they shop and at checkout confirmation.
Call `POST /api/payment_methods//confirm/` with the current cart contents, including non-WIC items (the response tells you which items are covered). See [POST /api/payment\_methods/{ref}/confirm/](./wic-api-reference.md#post-apipayment_methodsrefconfirm--wic-confirmation) for the full request and response spec.
Use the `benefit_usage` array from the response to show a real-time benefit tracker:
```text
Your WIC Benefits:
├─ Milk (Whole) ████████████ 2 of 2 gallons ✓ Used
├─ Milk (Broadband) ████████████ 1 of 1 gallon ✓ Used
├─ Eggs ░░░░░░░░░░░░ 0 of 1 dozen
└─ Fruits & Veg ████████░░░░ $11.00 of $11.00 ✓ Used
```
Items in `covered_items[]` will be purchased with WIC. Items in `uncovered_items[]` will not. Use the `indicator` field (`ineligible` or `balance_exhausted`) to explain why to the customer.
***
## Capture a WIC payment
You've already built [payment capture for EBT SNAP](https://docs.joinforage.app/docs/capture-ebt-payments-server-side). WIC capture uses the same flow with one key addition: you pass item-level details (UPC/PLU, price, quantity) instead of just a total amount.
> 📘 Best Capture Time
>
> Capture the WIC payment at checkout, not at fulfillment.
* **Refunds and substitutions handle fulfillment changes.** If an item is out of stock, you refund it. If you substitute an item, you refund the original and charge for the substitute. These operations don't require the customer to re-enter their PIN.
* **Benefits may expire.** WIC benefits have expiration dates. Capturing at checkout ensures the customer's benefits are used while they're still valid.
* **Session expiry.** The `balance_session` (which contains the verified PIN) expires at `pin_expires_at`. You must capture before this time to avoid needing the user to do another Balance Inquiry, re-evaluate the cart via WIC Confirmation, and attempt WIC Capture again.
**Payment lifecycle:**
```mermaid
flowchart TD
A["balance_session created
(PIN verified)"] --> B["POST /payments
(capture at checkout)"]
B --> C{"Capture result"}
C -- succeeded --> D["fulfillment"]
C -- failed --> E["❌ Stopped — no benefit change"]
D --> F["refund
(partial)"]
D --> G["substitute
(swap item)"]
D --> H["void
(full cancel)"]
classDef setup fill:#dbeafe,stroke:#2563eb,color:#1e3a5f
classDef decision fill:#fef3c7,stroke:#d97706,color:#78350f
classDef success fill:#d1fae5,stroke:#059669,color:#064e3b
classDef fail fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
class A,B setup
class C decision
class D,F,G,H success
class E fail
```
### Create a WIC payment
The [payment creation endpoint](https://docs.joinforage.app/reference/create-a-payment) is identical to EBT SNAP; use the same `POST /api/payments/` endpoint. Set `funding_type: "wic"` and omit the `amount` field (WIC settlement amounts are determined at capture, not creation).
See [POST /api/payments/](./wic-api-reference.md#post-apipayments--create-a-wic-payment) for the full request and response spec.
### Capture a WIC payment
The `covered_items` from the [WIC Confirmation](#the-wic-confirmation) indicate which items and quantities are eligible for WIC. Take those items, enrich them with each item's shelf price and any discounts, and pass to the capture request.
Call `POST /api/payments//capture_payment` with the `product_list`. See [POST /api/payments/{ref}/capture\_payment](./wic-api-reference.md#post-apipaymentsrefcapture_payment--capture-a-wic-payment) for the full request and response spec.
> 📘 CVB Items (Fruits & Vegetables)
>
> CVB items use a different pricing model. Set `price` to `1` and `quantity` to the total dollar amount. For example, $8.85 of bananas → `"price": 1, "quantity": 8.85`. CVB items also require the `plu` field instead of `gtin`.
***
## Interpret capture responses and handle failures
**To interpret a capture response:**
1. Inspect and interpret the top-level status value. It will be `succeeded` or `failed`.
1. If `succeeded` then the participant's WIC benefit has been decremented, the participant may take possession of the items in the WIC payment, and the merchant may expect a settlement.
2. If `failed` then the participant's WIC benefit has not been decremented and no settlement is due. Payments in `failed` status may be retried if remedied.
Payments succeed in their entirety or fail in their entirety. There are no partial successes or partial failures. The dollar amount due the merchant for an item may be changed at capture, however, if the amount given exceeds a predetermined not-to-exceed amount.
2. If the top-level status is `succeeded` then the `action_code` value for each item will be either `00` or `26`. A value of `26` indicates a price change.
3. If the top-level status is `failed` then the response will be an error with HTTP status `400`. The field `errors[].code` will have a top-level error code for the payment, such as `pin_expired` or `insufficient_funds`. If `insufficient_funds` then you will need to inspect the `action_code` of each item in the response. Expect `01`, `02`, or `04`.
The outcome lets you determine whether to fulfill the order, surface price adjustments to the customer, or prompt them to correct their cart and retry.
When interpreting a capture response, start with the overall response code:
* **`000` — Full Success.** Every item was approved exactly as requested. Benefit changes occurred.
* **`002` — Success with Price Adjustments.** The transaction succeeded and benefit changes occurred, but one or more items had NTE (Not to Exceed) price adjustments. The response will include those items with action code `26` so you can surface the adjusted amounts to the customer.
* **Any other code (e.g., `116`) — Failed.** No benefit changes occurred. This could be a top-level failure (e.g., invalid PIN) or one or more items were declined (e.g., `03` = insufficient units, `04` = UPC not prescribed). The response will include details on what failed so the customer can fix their cart before retrying.
The response uses exception reporting: only items that need attention are returned. If an item isn't in the response, it was approved as-is.
For full response scenarios with JSON examples, see [Capture Action Codes and Response Scenarios](./wic-api-reference.md#capture-action-codes-and-response-scenarios).
***
## Process refunds and item substitutions
WIC refunds use [the same endpoint as EBT SNAP](https://docs.joinforage.app/docs/ebt-refunds). The difference: pass item-level basket data and optionally specify substitutions for out-of-stock items.
After capturing a payment, use the refund endpoint to handle fulfillment changes, such as out-of-stock items, substitutions, weight adjustments, or full order cancellation. No PIN is required; all modifications chain off the original payment.
Refunds and Substitutions are **only for fulfillment time**. Once the customer receives the items, WIC does not permit returns or refunds. The only post-delivery resolution is an in-kind exchange (same brand, size, type) handled directly between the store and customer — no WIC transaction occurs.
If an order is modified during fulfillment, the customer must receive an updated WIC receipt reflecting the final purchase state.
| Type | When to use | Key field |
| --- | --- | --- |
| Full refund | Cancel the entire order before delivery | `"full_refund": true` |
| Partial refund | Remove or adjust specific items (out of stock, damaged, weight change) | `product_list` with items to refund |
| Substitution | Replace an item with an equivalent product | `substitute_with` on the item being replaced |
You can mix partial refunds and substitutions in a single request. Substitutions are more restrictive than plain refunds:
* **Same category and subcategory** — store brand whole milk → name brand whole milk is OK; 2% milk → whole milk is not.
* **Value cannot exceed original** — the substitute item's total value must be less than or equal to the original item's approved value.
* **Requires an active card** — plain refunds work even if the card is deactivated, but substitutions require the card to remain active.
For Refunds, the `product_list[].requested.price` must match the original item price supplied during capture.
Call `POST /payments/{payment_ref}/refund/` with the items to refund or substitute. See [POST /payments/{ref}/refund/](./wic-api-reference.md#post-paymentsrefrefund--refund-and-substitution) for the full request and response spec.
Requests are generally **all-or-nothing**, except for NTE outcomes. If any item fails validation (invalid UPC, incorrect substitution subcategory, or substitution value exceeds the item value), the entire request fails.
If substitution isn't possible (e.g., different sub-category needed), refund without `substitute_with` and prompt the customer for a new `balance_session` to purchase the substitute separately.
***
## Confirm WIC benefits before payment
**To confirm WIC benefits before payment:**
1. Call `POST /api/payment_methods//confirm/` one final time with the current cart state.
2. Display the confirmation to the customer: covered items, benefit breakdown by subcategory, and any items that will require additional payment (CVB overages, ineligible items).
3. Allow the customer to remove items from the WIC payment before proceeding.
The customer has reviewed and consented to how their benefits will be applied before you capture the payment.
***
## Apply discounts to WIC transactions
The core rule: **Apply discounts first, then transact WIC**. Merchants must offer WIC participants the same discounts available to all customers, even if there is no direct financial benefit to the participant. **When sending requests to Forage, set the "price" for each product to the net amount after discount**.
### Discount scenarios
#### 1. Item-level discount on a non-CVB item ("$1 off this milk")
**Scenario**: Milk costs $4.00, but has a $1.00 coupon
Send `3.00` as the product amount in the capture request.
```json
{
"product_list": [
{ "name": "Low fat milk", "gtin": 12345666, "price": 3.00, "quantity":1}
]
}
```
#### 2. Item-level discount on a CVB item ("$0.50 off carrots")
**Scenario**: Carrots cost $2.50, with a $0.50 discount
Send `2.00` as the product quantity in the capture request.
```json
{
"product_list": [
{ "name": "Carrots", "plu": 4555, "price": 1.00, "quantity":2.00}
]
}
```
#### 3. Transaction-wide discount ("20% off entire order")
**Scenario**: Cart has 20% discount applied to total
| Item | Original Price | Quantity | Subtotal |
| --- | --- | --- | --- |
| Milk | $4.00 | 1 | $4.00 |
| Cereal | $3.50 | 2 | $7.00 |
| Carrots (CVB) | $2.00 | 1 lb | $2.00 |
| **Total before discount** | | | **$13.00** |
| **20% discount** | | | **-$2.60** |
| **Total after discount** | | | **$10.40** |
**Calculate per-item discount**: Prorate the $2.60 discount proportionally across all items:
* Milk: $4.00 / $13.00 = 30.8% of cart → $2.60 × 0.308 = **$0.80 discount** → $3.20 net
* Cereal (each): $3.50 / $13.00 = 26.9% of cart → $2.60 × 0.269 = **$0.70 discount** → $2.80 net
* Carrots: $2.00 / $13.00 = 15.4% of cart → $2.60 × 0.154 = **$0.40 discount** → $1.60 net
**Send netted amounts**:
```json
{
"product_list": [
{ "name": "Milk", "gtin": 12345666, "price": 3.20, "quantity": 1 },
{ "name": "Cereal", "gtin": 867474384, "price": 2.80, "quantity": 2 },
{ "name": "Carrots", "plu": 4555, "price": 1.00, "quantity": 1.60 }
]
}
```
**Note**: For CVB items like Carrots, set `price: 1` and put the discounted dollar amount in `quantity`.
#### 4. Free item due to a discount
**Scenario**: Buy 2, get 1 free promotion for juice boxes that cost $3.00 each
Do not send Forage the free item, or the user's balance will be reduced by 3 juice boxes, even though they were supposed to receive 1 for free.
```json
{
"product_list": [
{ "name": "Juice", "gtin": 7578593493, "price": 3.00, "quantity":2}
]
}
```
#### 5. Transaction-level discount for mixed carts ("$10 off $50")
**Scenario**: Prorate the discount proportionally across all items in the cart, including WIC items.
*Example*: Cart has $40 WIC items + $60 non-WIC items = $100 total.
With $10 off:
* WIC items are 40% of cart → $4.00 discount applied to WIC items.
* Non-WIC items are 60% of cart → $6.00 discount applied to non-WIC items.
| | WIC Items | Non-WIC Items | Total |
| --- | --- | --- | --- |
| Cart value | $40.00 | $60.00 | $100.00 |
| Share of cart | 40% | 60% | 100% |
| Discount allocated | **$4.00** | **$6.00** | **$10.00** |
Distribute the $4.00 WIC discount proportionally across the individual WIC items.
In all cases, the net price after discount is what you send to Forage — the processor settles at that amount.
***
## Related documentation
* [WIC Integration Guide](./wic-overview.md) — guide set overview, audience, and prerequisites
* [How WIC Payments Work](./wic-payments.md) — voucher model, payment lifecycle, and straddle rules
* [WIC API Reference](./wic-api-reference.md) — endpoint specs, action codes, error codes, transaction limits, and receipt requirements