HomeGuidesReference↗ Forage Dashboard
Log In
Guides

WIC API Reference

Endpoint specs, action codes, error codes, transaction limits, and receipt requirements for the Forage WIC API.

🗺️

This WIC Integration Guide is composed of three parts:

[ How WIC Payments Work | Integrate WIC with Forage | 👉 WIC API Reference ]


Integration Component Summary

Your WIC integration builds on your existing EBT SNAP or EBT Cash work. Most components are shared and require only minor parameter changes; four are WIC-specific.

Integration ComponentStatusNotes
Card Tokenization✅ SharedUse POST /api/payment_methods/ with type: "wic".
Balance Inquiry & PIN✅ SharedCreates reusable session, returns prescription for WIC.
Payment Capture✅ SharedSame endpoint — add product_list[] for item details.
Refunds✅ SharedNo PIN required, chains off original payment. Add product_list[] for item details.
Payment Method Storage✅ SharedReusable tokens work identically.
Inventory Management⚠️ WICSync daily APL data, tag catalog by UPC/PLU.
Real-Time Eligibility⚠️ WICCall confirmation endpoint as cart changes.
Item Substitutions⚠️ WICReplace items during fulfillment with category matching. Requires item details with product_list[].
Prescription Tracking⚠️ WICDisplay category/subcategory balances, not dollar amounts.

GET /api/program_details/ — Agency and Card Details

WIC is administered by agencies—typically one per state, but tribal nations and territories have their own agencies. This endpoint returns EBT card details for each state as well, which is relevant to SNAP.

📘

Planned deprecation

Forage plans to deprecate the existing Retrieve Card Details Endpoint used for SNAP and replace it with this /program_details endpoint to accommodate both WIC and SNAP.

📘

agency_id = APL

The agency_id returned during card tokenization (via card.state) tells you which agency's APL applies to a given customer.

Response (200 OK)

FieldDescription
wic[]Array of WIC agency objects
wic[].agency_idUnique identifier for the WIC agency. Use this in subsequent API calls.
wic[].agency_nameHuman-readable agency name
wic[].binCard BIN prefix that routes to this agency
wic[].pan_lengthsSupported PAN lengths for this agency (e.g., [16] or [16, 19])
wic[].broadband_straddle_allowedWhether sub-category straddle is permitted (see How WIC Payments Work)
ebt[]Array of EBT state objects
ebt[].stateTwo-letter state code
ebt[].binCard BIN prefix that routes to this state
ebt[].pan_lengthsSupported PAN lengths for this state (e.g., [16] or [16, 19])
{
  "wic": [
    {
      "agency_id": "040",
      "agency_name": "Massachusetts WIC",
      "bin": "610320",
      "pan_lengths": [16, 19],
      "broadband_straddle_allowed": false
    },
    {
      "agency_id": "047",
      "agency_name": "Navajo Nation WIC",
      "bin": "610188",
      "pan_lengths": [16],
      "broadband_straddle_allowed": false
    }
  ],
  "ebt": [
    {
      "state": "MA",
      "bin": "600528",
      "pan_lengths": [16, 19]
    },
    {
      "state": "NJ",
      "bin": "610434",
      "pan_lengths": [16]
    }
  ]
}

GET /api/wic/categories/ — WIC Food Categories

Returns the category and subcategory structure for WIC eligible items. Use this to understand how prescription benefits are organized and to look up category_description values not included in APL responses.

Response (200 OK)

FieldDescription
categories[]Array of WIC categories
categories[].category_codeWIC category code
categories[].category_descriptionHuman-readable category name
categories[].subcategories[]Array of subcategories within this category
categories[].subcategories[].subcategory_codeWIC sub-category code (000 = Broadband)
categories[].subcategories[].subcategory_descriptionHuman-readable sub-category name
categories[].subcategories[].unit_of_measureUnit type: GALLON, DOZEN, OUNCE, DOLLARS, etc.
{
  "categories": [
    {
      "category_code": "03",
      "category_description": "Milk",
      "subcategories": [
        {
          "subcategory_code": "001",
          "subcategory_description": "Whole Milk",
          "unit_of_measure": "GALLON"
        },
        {
          "subcategory_code": "002",
          "subcategory_description": "2% Milk",
          "unit_of_measure": "GALLON"
        }
      ]
    },
    {
      "category_code": "19",
      "category_description": "Fruits and Vegetables",
      "subcategories": [
        {
          "subcategory_code": "001",
          "subcategory_description": "Fresh Fruits and Vegetables",
          "unit_of_measure": "DOLLARS"
        }
      ]
    }
  ]
}

GET /api/wic/apl/ — Approved Product List

Returns all UPC/PLU items eligible for WIC purchase within an agency. APLs refresh daily; sync this data with your product catalog on the same schedule.

See Integrate WIC with Forage: Sync APL Inventory Data for the sync procedure.

Endpoint

GET /api/wic/apl/?agency_id={id}&cursor={cursor}&limit={limit}&since={date}&upc={str}

Request Parameters

ParameterTypeDescription
agency_idOptional stringThe agency identifier (e.g., 040). If provided, results are scoped to items present in the agency's APL.
cursorstringPagination cursor (from next field in response)
limitOptional integerNumber of items per page (default: 1000, max: 5000)
sinceOptional stringIf provided, only items with an updated timestamp ≥ this value are returned. Otherwise all items are returned. Supports up to 30 days of history; if your data is more stale, fetch all APL data.
upcOptional stringIf provided, only the item matching this UPC is returned.

Response (200 OK)

FieldDescription
nextURL for next page of results, or null if no more pages
results[]Array of Products from WIC Agency APL files
results[].gtinThe GTIN for a WIC item
results[].pluThe PLU for an item
results[].eligible_agencies[]List of agency IDs the item is eligible for
results[].category_codeWIC category code (match against prescription). To get category_description, call GET /api/wic/categories/ and look up by [category_code][subcategory_code].category_description.
results[].nameHuman-readable product description
results[].unit_of_measureThe benefit accounting unit: GALLON, DOZEN, OUNCE, DOLLARS, etc. This is the unit the prescription tracks, not the product's package size. Cash Value Benefits (CVB): Category 19 (Fruits and Vegetables) uses dollar-based benefits. For these items unit_of_measure is DOLLARS, benefit_quantity is null, and the prescription specifies a dollar amount rather than a quantity.
results[].benefit_quantityHow many unit_of_measure units this product consumes from the benefit. A half-gallon of milk has benefit_quantity: 0.5 because it consumes 0.5 gallons. null for CVB items. Different package sizes of the same product consume different amounts: a 1-gallon jug has benefit_quantity: 1, a half-gallon has benefit_quantity: 0.5.
{
  "next": "?cursor=eyJndGluIjoiMDEyMzQ1Njc4OTAxIn0&limit=1000",
  "results": [
    {
      "gtin": "012345678901",
      "name": "Whole Milk 1 Gallon",
      "category_code": "03",
      "subcategory_code": "001",
      "unit_of_measure": "GALLON",
      "benefit_quantity": 1,
      "eligible_agencies": [
        "007": {"straddle": true},
        "078": {"straddle": false}
      ]
    },
    {
      "gtin": "011110089247",
      "name": "Whole Milk Half Gallon",
      "category_code": "03",
      "subcategory_code": "001",
      "unit_of_measure": "GALLON",
      "benefit_quantity": 0.5,
      "eligible_agencies": [
        "007": {"straddle": true},
        "063": {"straddle": false}
      ]
    },
    {
      "plu": "4111",
      "name": "Fresh Bananas",
      "category_code": "19",
      "subcategory_code": "001",
      "unit_of_measure": "DOLLARS",
      "benefit_quantity": null,
      "eligible_agencies": [
        "007": {"straddle": true},
        "063": {"straddle": false},
        "023": {"straddle": false}
      ]
    }
  ]
}

POST /api/payment_methods/ — Tokenize a WIC Card

Tokenizes a WIC card for use in subsequent balance inquiries and payments. Uses the same endpoint as EBT SNAP tokenization—set type to "wic". For test card numbers, see test cards.

See Integrate WIC with Forage: Tokenize a WIC Card for the full procedure.

Request

FieldTypeDescription
typestringMust be "wic"
reusablebooleanSet to true to reuse this payment method across sessions
card.numberstringThe full WIC card number (16–19 digits)
{
  "type": "wic",
  "reusable": true,
  "card": {
    "number": "6104021234567890"
  }
}

Response (201 Created)

FieldDescription
refUnique identifier for this payment method. Use this in subsequent API calls.
typeThe tender type of the Payment Method
reusableWhether or not the Payment Method can be used for subsequent purchases
card.last_4Last 4 digits of the card (for display purposes)
card.agency_idID of WIC Agency this card belongs to. Determines which APL and benefit rules apply.
card.agency_nameHuman-readable WIC Agency Name
card.createdTime the card was tokenized
{
  "ref": "a1b2c3d4e5",
  "type": "wic",
  "reusable": true,
  "card": {
    "last_4": "7890",
    "agency_id": "007",
    "agency_name": "California WIC",
    "created": "2026-01-28T10:30:45Z"
  }
}

POST + GET /api/balance_sessions/ — Balance Inquiry Endpoints

Two endpoints form the balance inquiry flow: POST to create the session, GET to retrieve the prescription after PIN entry.

See Integrate WIC with Forage: Perform a Balance Inquiry for the full procedure.

POST /api/balance_sessions/ — Create a Balance Session

Request

FieldTypeDescription
payment_methodstringThe ref returned when you created the payment method
success_redirect_urlstringWhere to redirect the customer after successful PIN entry
cancel_redirect_urlstringWhere to redirect if the customer cancels or PIN entry fails
{
  "payment_method": "a1b2c3d4e5",
  "success_redirect_url": "https://your-app.com/wic/balance-success",
  "cancel_redirect_url": "https://your-app.com/wic/balance-cancel"
}

Response (200 OK)

FieldDescription
refThe balance session reference. Use this to retrieve results after PIN entry.
payment_methodThe payment method token associated with this balance check.
success_redirect_urlThe URL the customer is redirected to after successful PIN entry.
cancel_redirect_urlThe URL the customer is redirected to if they cancel PIN entry.
is_activeWhether this session is still valid.
redirect_urlRedirect the customer here for PIN entry. Forage handles PIN capture securely.
{
  "ref": "93410bcaff",
  "payment_method": "a1b2c3d4e5",
  "success_redirect_url": "https://your-app.com/wic/balance-success",
  "cancel_redirect_url": "https://your-app.com/wic/balance-cancel",
  "is_active": true,
  "redirect_url": "https://checkout.joinforage.app/balance?session=93410bcaff&merchant=1234567"
}
📘

Query Parameters

Forage appends balance_session_ref as a query parameter to the redirect URL. If your URL already contains query parameters, Forage will append using & instead of ?.

GET /api/balance_sessions/{ref}/ — Retrieve a Balance Session

Read the balance_session_ref from the redirect query parameter, then call this endpoint to retrieve the customer's prescription.

Response (200 OK)

FieldDescription
refThe balance session reference. Pass this to subsequent payment capture and order modification calls. This links to the verified PIN and avoids requiring the customer to re-enter their PIN.
payment_methodThe payment method this session is associated with
pin_expires_atWhen this PIN-reuse session expires. You must capture the payment before this time.
benefit_end_dateThe last date these benefits can be used. Display this to the customer if they're close to expiration.
prescription[]Array of available benefits by category
prescription[].category_codeWIC category code (use for APL matching)
prescription[].category_descriptionHuman-readable description of the category
prescription[].subcategory_codeWIC sub-category code (use for APL matching)
prescription[].subcategory_descriptionHuman-readable description of the subcategory
prescription[].quantity_availableUnits available for quantity-based benefits (e.g., 3 gallons) and for CVB items (e.g., $3.00)
prescription[].unit_of_measureThe unit of measure for a specific (Category, SubCategory) WIC prescription
{
  "ref": "93410bcaff",
  "payment_method": "a1b2c3d4e5",
  "pin_expires_at": "2026-01-28T18:30:00Z",
  "benefit_end_date": "2026-02-28",
  "prescription": [
    {
      "category_code": "03",
      "category_description": "Milk",
      "subcategory_code": "001",
      "subcategory_description": "Low Fat Milk",
      "quantity_available": 3.0,
      "unit_of_measure": "GALLON"
    },
    {
      "category_code": "06",
      "category_description": "Cheese",
      "subcategory_code": "004",
      "subcategory_description": "Tofu",
      "quantity_available": 1.0,
      "unit_of_measure": "OZ"
    },
    {
      "category_code": "31",
      "category_description": "Infant Formula",
      "subcategory_code": "001",
      "subcategory_description": "Nutramigen",
      "quantity_available": 11.0,
      "unit_of_measure": "LBS"
    }
  ]
}

Balance Session Lifetime and Scope

The balance session ref remains valid until pin_expires_at. During this window, you can:

  • Repeatedly request the cart's WIC Confirmation
  • Capture a payment
  • Modify the order during fulfillment

All operations within the session use the originally verified PIN. The customer does not need to re-enter their PIN.


POST /api/payment_methods/{ref}/confirm/ — WIC Confirmation

Returns a real-time breakdown of which items in the cart are covered by the customer's prescription. Call this endpoint on every cart change and again before checkout.

See Integrate WIC with Forage: Build the WIC-Eligible Cart for the full procedure.

Endpoint

POST /api/payment_methods/<payment_method_ref>/confirm/

Request

FieldTypeDescription
payment_method_refstringThe payment method ref involved in the Balance Inquiry. Note: The WIC balance must already be checked before calling /confirm/.
cart[].gtinstringProduct GTIN for quantity-based WIC items
cart[].plustringPLU for Cash Value Benefit (CVB) WIC items
cart[].namestringProduct name
cart[].quantitynumberNumber of units. CVB items (Category Code 019, e.g., Fruits & Vegetables): The per-unit price is $1.00, so quantity captures the total dollar amount for that UPC/PLU.
{
  "payment_method_ref": "abcdef1234",
  "cart": [
    {
      "gtin": "012345678901",
      "name": "Whole Milk 1 Gallon",
      "quantity": 2
    },
    {
      "gtin": "098765432109",
      "name": "2% Milk 1 Gallon",
      "quantity": 1
    },
    {
      "plu": "4011",
      "name": "Bananas",
      "quantity": 14.45
    },
    {
      "gtin": "049000042566",
      "name": "Coca-Cola 12oz",
      "quantity": 1
    },
    {
      "gtin": "011110089247",
      "name": "Whole Milk Half Gallon",
      "quantity": 1
    }
  ]
}

Response (200 OK)

FieldDescription
payment_methodReference to the Payment Method
pin_expires_atTimestamp indicating when the PIN entry session expires
benefit_end_dateDate when the current benefit period ends
benefit_usage[]Per-subcategory breakdown of benefits used and remaining based on covered items
benefit_usage[].category_codeWIC category code (e.g., "03" for Milk)
benefit_usage[].category_descriptionHuman-readable category name
benefit_usage[].subcategory_codeWIC subcategory code ("000" indicates broadband)
benefit_usage[].subcategory_descriptionHuman-readable subcategory name
benefit_usage[].unit_of_measureUnit for the quantity fields (e.g., GALLON, DOLLARS)
benefit_usage[].quantity_availableInitial balance in the cardholder's prescription for this (Category, Subcategory)
benefit_usage[].redemption_quantityQuantity that will be redeemed for this subcategory
covered_items[]WIC-eligible items covered by the available prescription. An item may be partially covered; see indicator.
covered_items[].gtinThe GTIN for a WIC item. One of gtin or plu will be present.
covered_items[].pluThe PLU for a WIC item. Present instead of gtin for CVB items.
covered_items[].nameHuman-readable item name
covered_items[].quantityQuantity of this item anticipated to be covered by WIC if capturing this exact basket
covered_items[].indicatorAlways covered for items in this array
uncovered_items[]Items and quantities not anticipated to be fully covered by WIC benefits. For partial coverage, the covered portion appears in covered_items[].quantity and the uncovered portion appears here.
uncovered_items[].gtinThe GTIN of the item. One of gtin or plu will be present.
uncovered_items[].pluThe PLU of the item
uncovered_items[].nameHuman-readable item name
uncovered_items[].quantityTotal quantity the prescription will not cover for this item
uncovered_items[].indicatorReason this quantity is not covered. Never covered.

Item indicator values

ValueDescription
coveredThis item is covered by the customer's WIC prescription
ineligibleThis item is not covered by WIC because it is not present in the WIC agency's APL
balance_exhaustedThis item is not covered by WIC because the cardholder has insufficient balance in that item's category
{
  "payment_method": "a1b2c3d4e5",
  "pin_expires_at": "2026-01-28T18:30:00Z",
  "benefit_end_date": "2026-02-28",
  "benefit_usage": [
    {
      "category_code": "03",
      "category_description": "Milk",
      "subcategory_code": "001",
      "subcategory_description": "Whole Milk",
      "unit_of_measure": "GALLON",
      "quantity_available": 2.0,
      "redemption_quantity": 2.0
    },
    {
      "category_code": "03",
      "category_description": "Milk",
      "subcategory_code": "000",
      "subcategory_description": "Broadband Milk",
      "unit_of_measure": "GALLON",
      "quantity_available": 1.0,
      "redemption_quantity": 1.0
    },
    {
      "category_code": "19",
      "category_description": "Fruits and Vegetables",
      "unit_of_measure": "DOLLARS",
      "quantity_available": 11.0,
      "redemption_quantity": 11.0
    }
  ],
  "covered_items": [
    {
      "gtin": "012345678901",
      "name": "Whole Milk 1 Gallon",
      "quantity": 2,
      "indicator": "covered"
    },
    {
      "gtin": "098765432109",
      "name": "2% Milk 1 Gallon",
      "quantity": 1,
      "indicator": "covered"
    },
    {
      "plu": "4011",
      "name": "Bananas",
      "quantity": 14.45,
      "indicator": "covered"
    }
  ],
  "uncovered_items": [
    {
      "gtin": "049000042566",
      "name": "Coca-Cola 12oz",
      "quantity": 1,
      "indicator": "ineligible"
    },
    {
      "gtin": "011110089247",
      "name": "Whole Milk Half Gallon",
      "quantity": 1,
      "indicator": "balance_exhausted"
    }
  ]
}

POST /api/payments/ — Create a WIC Payment

Uses the same endpoint as EBT SNAP payment creation. Set funding_type to "wic" and omit the amount field. WIC settlement amounts are determined at capture.

See Integrate WIC with Forage: Capture a WIC Payment for the full procedure.

Request

See Create a Payment for the full request body. Note the following differences from SNAP:

FieldChanges for WIC
funding_typeMust be set to "wic"
amountNot required for WIC payments. The settlement amount can change after capture due to price adjustments. Set on the payment after capture.
{
  "funding_type": "wic",
  "description": "WIC Payment for Test Customer",
  "payment_method": "abcd1234"
}

Response

Payment details echoed back. See Create a Payment for standard fields.

{
  "ref": "6edd2dcda2",
  "funding_type": "wic",
  "description": "WIC Payment for Test Customer",
  "payment_method": "abcd1234",
  "created": "2026-01-22T15:38:20.648194-08:00",
  "status": "requires_confirmation",
  "expires_at": "2026-01-23T00:08:20.648216Z"
}

POST /api/payments/{ref}/capture_payment — Capture a WIC Payment

Captures a WIC payment. Takes the covered items from the WIC Confirmation response, enriches them with shelf prices and any discounts, and submits them for processing. For the base capture pattern for EBT SNAP, see server-side capture for EBT SNAP.

See Integrate WIC with Forage: Capture a WIC Payment for the full procedure.

Endpoint

POST /api/payments/<payment_ref>/capture_payment

Request

FieldDescription
product_list[]Array of product items included in the request
product_list[].nameHuman-readable product name (e.g., "Low fat milk", "Bananas")
product_list[].gtinGlobal Trade Item Number (GTIN) identifying a packaged product (e.g., 12345666)
product_list[].pluPrice Look-Up code identifying a non-packaged item, typically produce (e.g., 4321)
product_list[].priceUnit price of the item in dollars (e.g., 5.00)
product_list[].quantityQuantity of this item
📘

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. Some CVB items require the plu field instead of gtin.

{
  "product_list": [
    {
      "name": "Low fat milk",
      "gtin": 12345666,
      "price": 5.0,
      "quantity": 1
    },
    {
      "name": "Bananas",
      "plu": 4321,
      "price": 1,
      "quantity": 8.85
    }
  ]
}

Response

Standard payment fields echoed back, plus the following fields present only on captured payments:

FieldTypeDescription
amountstringTotal dollar amount to be settled by the WIC processor. Only present on succeeded responses.
statusstring"succeeded", "failed", "canceled", "pending"
product_list[]arrayOne entry per item in the original request
product_list[].namestringProduct display name
product_list[].gtinintegerGlobal Trade Item Number. Echoed from request.
product_list[].pluintegerPrice Look-Up code. Present instead of gtin for CVB items.
product_list[].requestedobjectEcho of the original request price and quantity
requested.pricenumberPrice submitted in the request
requested.quantitynumberQuantity submitted in the request
product_list[].approvedobjectProcessor's decision for this item
approved.pricenumberApproved unit price. May be lower than requested due to NTE rules. 0 if declined.
approved.quantitynumberApproved quantity. 0 if declined.
approved.action_codestringProcessor result code (see Capture Action Codes)
approved.action_messagestringHuman-readable explanation of the decision
receiptobjectWIC transaction receipt details to communicate to the WIC customer
receipt.prescriptionarrayCurrent benefit balances after the transaction
prescription[].category_codestringWIC food category identifier
prescription[].category_descriptionstringHuman-readable category name
prescription[].subcategory_codestringSubcategory identifier within the category
prescription[].subcategory_descriptionstringHuman-readable subcategory name
prescription[].quantity_availablenumberRemaining units in this benefit subcategory
prescription[].unit_of_measurestringUnit type for quantity_available — e.g., "GALLON", "DOLLARS"
{
  "amount": "5.00",
  "status": "succeeded",
  "product_list": [
    {
      "name": "Low fat milk",
      "gtin": 12345666,
      "requested": {
        "price": 5.0,
        "quantity": 1
      },
      "approved": {
        "price": 5.0,
        "quantity": 1,
        "action_code": "00",
        "action_message": "Approved"
      }
    }
  ],
  "receipt": {
    "prescription": [
      {
        "category_code": "03",
        "category_description": "Milk",
        "subcategory_code": "001",
        "subcategory_description": "Low fat Milk",
        "quantity_available": 7.0,
        "unit_of_measure": "GALLON"
      }
    ]
  },
  "ref": "6edd2dcda2",
  "funding_type": "wic",
  "description": "WIC Payment for Test Customer",
  "payment_method": "abcd1234",
  "created": "2026-01-22T15:38:20.648194-08:00",
  "status": "succeeded",
  "expires_at": "2026-01-23T00:08:20.648216Z"
}

Capture Action Codes and Response Scenarios

Overall Response Codes

The top-level capture response indicates the transaction outcome:

  • 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 includes those items with action code 26.
  • 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 declined. The response includes details on what failed.

The response uses exception reporting: only items that need attention are returned. If an item is not in the response, it was approved as-is.

Item-Level Action Codes

CodeDescriptionSuggested customer-facing message
00Approved
01Category not prescribed"One or more items in your cart aren't covered by your WIC benefits."
02Sub-category not prescribed"One or more items in your cart aren't covered by your WIC benefits."
03Insufficient units"Your WIC benefits don't cover the full quantity of one or more items in your cart."
04UPC/PLU not prescribed"One or more items in your cart aren't eligible for WIC."
26Approved for a lower price due to NTE price exceeded"We adjusted one or more items in your cart to the maximum WIC-covered price."

Success Response Summary

ResponseStatusNote
Full Approval (Quantity)succeeded
Full Approval (CVB)succeeded
Partial Approval (NTE)succeededItem(s) exceeded the NTE limit and were approved at the capped price
Straddle AppliedsucceededPurchase draws from multiple benefit subcategories

Failure Response Summary

ResponseStatusLikely CauseRecommended ActionSuggested customer-facing message
Insufficient BalancefailedMismatch between the site's eligible items and the customer's actual prescription balanceAsk the customer to redo their balance check. If balance is accurate, the store's WIC catalog may be mislabeled."Your WIC benefits don't cover all the items in your cart. Please review your cart and try again."
Category Not PrescribedfailedThe site has an item mapped to a benefit category the customer doesn't haveVerify the store's WIC item catalog. If catalog is correct, ask the customer to redo their balance check."One or more items in your cart aren't covered by your WIC benefits. Please remove them and try again."
Item Not WIC EligiblefailedStore's WIC catalog incorrectly flags a non-eligible item as eligibleFix the item's WIC eligibility in the store catalog."One or more items in your cart aren't eligible for WIC. Please remove them and try again."
Mixed BasketfailedAt least one item was declined, causing the entire WIC transaction to failIdentify and resolve the specific item(s) that triggered the decline."Your WIC benefits couldn't cover one or more items in your cart. Please review your cart and try again."
PIN ExpiredfailedToo much time elapsed between the customer's PIN entry and the capture attemptAsk the customer to re-enter their PIN and complete a new balance check."Your session has expired. Please re-enter your PIN and try again."

Full Approval — Quantity-Based Item

A standard item is requested and approved without adjustments. The participant has sufficient balance in the relevant category.

// Request
{
  "product_list": [{ "name": "Low fat milk", "gtin": 12345666, "price": 5.0, "quantity": 1 }]
}
// Response — approved.price and approved.quantity match the request
{
  "amount": "5.00",
  "status": "succeeded",
  "product_list": [
    {
      "name": "Low fat milk",
      "gtin": 12345666,
      "requested": { "price": 5.0, "quantity": 1 },
      "approved": {
        "price": 5.0,
        "quantity": 1,
        "action_code": "00",
        "action_message": "Approved"
      }
    }
  ],
  "receipt": {
    "prescription": [
      {
        "category_code": "03",
        "category_description": "Milk",
        "subcategory_code": "001",
        "subcategory_description": "Low fat Milk",
        "quantity_available": 7.0,
        "unit_of_measure": "GALLON"
      }
    ]
  }
}

Full Approval — CVB Item (Cash Value Benefit)

CVB items, such as fresh fruits and vegetables, are measured in dollars rather than units.

// Request — note: price is 1, quantity is the dollar amount, and plu is used instead of gtin
{
  "product_list": [{ "name": "Bananas", "plu": 4566, "price": 1, "quantity": 8.85 }]
}
// Response
{
  "amount": "8.85",
  "status": "succeeded",
  "product_list": [
    {
      "name": "Bananas",
      "plu": 4566,
      "requested": { "price": 1, "quantity": 8.85 },
      "approved": {
        "price": 1,
        "quantity": 8.85,
        "action_code": "00",
        "action_message": "Approved"
      }
    }
  ],
  "receipt": {
    "prescription": [
      {
        "category_code": "08",
        "category_description": "Fruits and vegetables",
        "subcategory_code": "002",
        "subcategory_description": "Fresh",
        "quantity_available": 3.0,
        "unit_of_measure": "DOLLARS"
      }
    ]
  }
}

Partial Approval — NTE Price Adjustment

The requested price exceeds the Not-To-Exceed (NTE) limit for the item. The processor approves the item but reduces the price to the NTE ceiling. The quantity is unchanged. Your settlement amount should use amount from the response, not the original requested price.

// Request — price of $35 exceeds the NTE limit
{
  "product_list": [{ "name": "Low fat milk", "gtin": 12345666, "price": 35.0, "quantity": 1 }]
}
// Response — price reduced from $35.00 → $7.00 (the NTE limit)
{
  "amount": "7.00",
  "status": "succeeded",
  "product_list": [
    {
      "name": "Low fat milk",
      "gtin": 12345666,
      "requested": { "price": 35.0, "quantity": 1 },
      "approved": {
        "price": 7.0,
        "quantity": 1,
        "action_code": "26",
        "action_message": "Approved for a lower price due to NTE rules"
      }
    }
  ],
  "receipt": {
    "prescription": [
      {
        "category_code": "03",
        "category_description": "Milk",
        "subcategory_code": "001",
        "subcategory_description": "Low fat Milk",
        "quantity_available": 5.0,
        "unit_of_measure": "GALLON"
      }
    ]
  }
}

Special Approval — Straddle Applied

A straddle occurs when a participant's specific subcategory balance is insufficient, so the processor pulls remaining units from the broadband subcategory (000). You can identify a straddle by checking receipt.prescription for multiple decremented entries, one of which has subcategory_code: "000".

// Request — a normal single-item request
{
  "product_list": [{ "name": "Low fat milk", "gtin": 12345666, "price": 5.0, "quantity": 1 }]
}
// Response — two prescription lines were affected
{
  "amount": "5.00",
  "status": "succeeded",
  "product_list": [
    {
      "name": "Low fat milk",
      "gtin": 12345666,
      "requested": { "price": 5.0, "quantity": 1 },
      "approved": {
        "price": 5.0,
        "quantity": 1,
        "action_code": "00",
        "action_message": "Approved"
      }
    }
  ],
  "receipt": {
    "prescription": [
      {
        "category_code": "03",
        "category_description": "Milk",
        "subcategory_code": "001",
        "subcategory_description": "Low fat Milk",
        "quantity_available": 0.0,
        "unit_of_measure": "GALLON"
      },
      {
        "category_code": "03",
        "category_description": "Milk",
        "subcategory_code": "000",
        "subcategory_description": "Generic Milk Broadband",
        "quantity_available": 8.0,
        "unit_of_measure": "GALLON"
      }
    ]
  }
}

Decline — Insufficient Balance

The participant's remaining balance in the category cannot cover the requested quantity. The entire item is declined.

// Request — 8 gallons requested, but participant only has 5 remaining
{
  "product_list": [{ "name": "Low fat milk", "gtin": 12345666, "price": 7.0, "quantity": 8 }]
}
// Response — action_code "03", approved price and quantity are both 0
{
  "status": "failed",
  "product_list": [
    {
      "name": "Low fat milk",
      "gtin": 12345666,
      "requested": { "price": 7.0, "quantity": 8 },
      "approved": {
        "price": 0.0,
        "quantity": 0,
        "action_code": "03",
        "action_message": "Insufficient benefit units available"
      }
    }
  ],
  "receipt": {
    "prescription": [
      {
        "category_code": "03",
        "category_description": "Milk",
        "subcategory_code": "001",
        "subcategory_description": "Low fat Milk",
        "quantity_available": 5.0,
        "unit_of_measure": "GALLON"
      }
    ]
  }
}

Decline — Category Not Prescribed

The participant does not have a benefit prescription for the requested item's category.

// Request
{
  "product_list": [{ "name": "Infant Formula", "gtin": 485812345, "price": 11.0, "quantity": 1 }]
}
// Response — action_code "01"; receipt still shows existing prescriptions, not the missing one
{
  "status": "failed",
  "product_list": [
    {
      "name": "Infant Formula",
      "gtin": 485812345,
      "requested": { "price": 11.0, "quantity": 1 },
      "approved": {
        "price": 0,
        "quantity": 0,
        "action_code": "01",
        "action_message": "Category not prescribed"
      }
    }
  ],
  "receipt": {
    "prescription": [
      {
        "category_code": "03",
        "category_description": "Milk",
        "subcategory_code": "001",
        "subcategory_description": "Low fat Milk",
        "quantity_available": 5.0,
        "unit_of_measure": "GALLON"
      }
    ]
  }
}

Decline — Item Not WIC Eligible

The product's UPC is not on the Approved Product List (APL).

// Request
{
  "product_list": [{ "name": "Soda", "gtin": 98765432, "price": 6.99, "quantity": 1 }]
}
// Response — action_code "04"
{
  "status": "failed",
  "product_list": [
    {
      "name": "Soda",
      "gtin": 98765432,
      "requested": { "price": 6.99, "quantity": 1 },
      "approved": {
        "price": 0.0,
        "quantity": 0,
        "action_code": "04",
        "action_message": "UPC not prescribed"
      }
    }
  ],
  "last_processing_error": {
    "code": "item_not_prescribed",
    "message": "Attempted to purchase an item that is not eligible according to the user's prescription",
    "source": {
      "resource": "Payments",
      "ref": "{payment_ref}"
    }
  }
}

Decline — Mixed Basket

If any item in the basket is not covered, the entire purchase fails—even items that would otherwise be approved.

// Request — milk is eligible, but infant formula is not prescribed for this participant
{
  "product_list": [
    { "name": "Low fat milk", "gtin": 12345666, "price": 5.0, "quantity": 1 },
    { "name": "Infant Formula", "gtin": 485812345, "price": 11.0, "quantity": 1 }
  ]
}
// Response — status is "failed" even though milk was individually "Approved"
{
  "status": "failed",
  "product_list": [
    {
      "name": "Low fat milk",
      "gtin": 12345666,
      "requested": { "price": 5.0, "quantity": 1 },
      "approved": {
        "price": 5.0,
        "quantity": 1,
        "action_code": "00",
        "action_message": "Approved"
      }
    },
    {
      "name": "Infant Formula",
      "gtin": 485812345,
      "requested": { "price": 11.0, "quantity": 1 },
      "approved": {
        "price": 0,
        "quantity": 0,
        "action_code": "01",
        "action_message": "Category not prescribed"
      }
    }
  ],
  "last_processing_error": {
    "code": "category_not_prescribed",
    "message": "Category of item is not in user's prescription",
    "source": {
      "resource": "Payments",
      "ref": "{payment_ref}"
    }
  },
  "receipt": {
    "prescription": [
      {
        "category_code": "03",
        "category_description": "Milk",
        "subcategory_code": "001",
        "subcategory_description": "Low fat Milk",
        "quantity_available": 5.0,
        "unit_of_measure": "GALLON"
      }
    ]
  }
}

POST /payments/{ref}/refund/ — Refund and Substitution

Handles fulfillment changes: partial refunds for out-of-stock or weight-adjusted items, full cancellations, and item substitutions. No PIN is required; all modifications chain off the original payment. For the base refund mechanics for EBT SNAP, see EBT SNAP refunds.

See Integrate WIC with Forage: Process Refunds and Item Substitutions for the full procedure.

Refund Types

TypeWhen to UseKey Field
Full refundCancel the entire order before delivery"full_refund": true
Partial refundRemove or adjust specific items (out of stock, damaged, weight change)product_list with items to refund
SubstitutionReplace an item with an equivalent productsubstitute_with on the item being replaced

Substitution Constraints

Substitutions are more restrictive than plain refunds:

  • Same category and subcategory — store brand whole milk → name brand whole milk is valid; 2% milk → whole milk is not.
  • Value cannot exceed original — the substitute item's total value must be ≤ 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.

Endpoint

POST /payments/{payment_ref}/refund/

Request

FieldTypeRequiredDescription
product_listarrayNoItems to refund or substitute. Required if full_refund is not set.
product_list[].gtinstringYesGTIN from the original payment. Use plu instead for CVB items.
product_list[].plustringYesPLU from the original payment. Use instead of gtin for CVB items.
product_list[].quantitynumberYesUnits to refund. For CVB items, this is the dollar amount.
product_list[].pricenumberYesMust match the approved price from the original payment (including any NTE adjustment).
product_list[].reasonstringYesItem-level reason: out_of_stock, damaged, weight_adjustment
product_list[].substitute_withobjectNoReplacement item. Omit for a plain refund.
substitute_with.gtinstringYesSubstitute product GTIN (or plu for CVB items)
substitute_with.plustringYesSubstitute product PLU. Use instead of gtin for CVB items.
substitute_with.quantitynumberYesSubstitute quantity (dollar amount for CVB items)
substitute_with.pricenumberYesSubstitute unit price
// Partial refund with substitution — include only the items being refunded or substituted
{
  "product_list": [
    {
      "gtin": "012345678901",
      "quantity": 1,
      "price": 5.99,
      "reason": "out_of_stock",
      "substitute_with": {
        "gtin": "012345678902",
        "quantity": 1,
        "price": 5.49
      }
    },
    {
      "plu": "0231",
      "quantity": 1.23,
      "price": 1,
      "reason": "out_of_stock",
      "substitute_with": {
        "plu": "0222",
        "quantity": 1.23,
        "price": 1
      }
    }
  ]
}

Response

The response follows the same product_list[].requested / product_list[].approved pattern as capture payments. For substitutions, a substituted_with block appears on the affected item.

FieldTypeDescription
refstringUnique identifier for this refund transaction
payment_refstringReference to the original payment being modified
statusstring"succeeded", "processing", or "failed"
amountnumberTotal dollar amount of the refund. For substitutions, reflects the net difference between original and substitute items.
product_list[]arrayOne entry per item in the refund request
product_list[].namestringProduct display name
product_list[].gtinstringGTIN from the original payment. One of gtin or plu will be present.
product_list[].plustringPLU from the original payment. Present instead of gtin for CVB items.
product_list[].requestedobjectThe items in need of refunding or substitution
product_list[].requested.pricenumberShelf price of the item to be refunded. Must match the original capture price.
product_list[].requested.quantitynumberQuantity of this gtin or plu to refund or substitute
product_list[].approvedobjectProcessor's decision for this item
product_list[].approved.pricenumberApproved refund price. 0 if declined.
product_list[].approved.quantitynumberApproved refund quantity. 0 if declined.
product_list[].approved.action_codestringProcessor result code
product_list[].approved.action_messagestringHuman-readable explanation of the decision
product_list[].substituted_withobjectReplacement item details. Only present if a substitution was requested and processed.
substituted_with.gtinstringGTIN of the substituted item. One of gtin or plu will be present.
substituted_with.plustringPLU of the substituted item. Present instead of gtin for CVB items.
substituted_with.namestringSubstitute product display name
substituted_with.requestedobjectEcho of the substitute item values sent to the processor
substituted_with.requested.pricenumberRequested substitute price
substituted_with.requested.quantitynumberRequested substitute quantity
substituted_with.approvedobjectProcessor's decision on the substitute item
substituted_with.approved.pricenumberApproved substitute price. 0 if declined.
substituted_with.approved.quantitynumberApproved substitute quantity. 0 if declined.
substituted_with.approved.action_codestringProcessor result code for the substitute
substituted_with.approved.action_messagestringHuman-readable explanation for the substitute decision
receiptobjectPost-refund benefit state
receipt.prescriptionarrayUpdated benefit balances after the refund (unchanged if refund failed)
receipt.prescription[].category_codestringWIC food category identifier (e.g., "03" for Milk)
receipt.prescription[].category_descriptionstringHuman-readable category name
receipt.prescription[].subcategory_codestringSubcategory identifier within the category
receipt.prescription[].subcategory_descriptionstringHuman-readable subcategory name
receipt.prescription[].quantity_availablenumberRemaining units in this benefit subcategory
receipt.prescription[].unit_of_measurestringUnit type — e.g., "GALLON", "DOLLARS"
{
  "ref": "refund_abc123",
  "payment_ref": "pay_xyz789",
  "status": "succeeded",
  "amount": 6.22,
  "product_list": [
    {
      "name": "Horizon Organic Whole Milk 1 Gallon",
      "gtin": "012345678901",
      "requested": { "price": 5.0, "quantity": 1 },
      "approved": {
        "price": 5.0,
        "quantity": 1,
        "action_code": "00",
        "action_message": "Approved"
      },
      "substituted_with": {
        "gtin": "012345678902",
        "name": "Store Brand Whole Milk 1 Gallon",
        "requested": { "price": 4.99, "quantity": 1 },
        "approved": {
          "price": 4.99,
          "quantity": 1,
          "action_code": "00",
          "action_message": "Approved"
        }
      }
    },
    {
      "name": "Brand A Strawberries",
      "plu": "0231",
      "requested": { "price": 1, "quantity": 1.23 },
      "approved": {
        "price": 1,
        "quantity": 1.23,
        "action_code": "00",
        "action_message": "Approved"
      },
      "substituted_with": {
        "plu": "0222",
        "name": "Brand B Strawberries",
        "requested": { "price": 1, "quantity": 1.23 },
        "approved": {
          "price": 1,
          "quantity": 1.23,
          "action_code": "00",
          "action_message": "Approved"
        }
      }
    }
  ],
  "receipt": {
    "prescription": [
      {
        "category_code": "03",
        "category_description": "Milk",
        "subcategory_code": "002",
        "subcategory_description": "Whole Milk",
        "quantity_available": 7.0,
        "unit_of_measure": "GALLON"
      },
      {
        "category_code": "19",
        "category_description": "Fruits and vegetables",
        "subcategory_code": "002",
        "subcategory_description": "Fresh",
        "quantity_available": 0.0,
        "unit_of_measure": "DOLLARS"
      }
    ]
  }
}

Refund Error Codes

When a refund fails, the response includes either a top-level errors array (for validation failures caught before processing) or a refund_errors array alongside the product_list (for item-level failures from the processor).

Error CodeCauseResolution
refund_category_mismatchSubstitute item is in a different WIC category or subcategory than the originalChoose a substitute within the same category and subcategory
item_refund_quantity_exceeds_original_quantityRefund quantity is greater than the originally purchased quantityReduce quantity to match or be less than the original
item_refund_price_mismatchRefund price doesn't match the approved price from the original paymentUse the approved.price from the original capture response, not the shelf price
refund_item_not_in_original_paymentGTIN/PLU was not part of the original transactionVerify the item identifier against the original payment's product_list
{
  "ref": "refund_abc123",
  "payment_ref": "pay_xyz789",
  "status": "failed",
  "amount": 40.0,
  "product_list": [
    {
      "name": "Horizon Organic Whole Milk 1 Gallon",
      "gtin": "012345678901",
      "requested": { "price": 4.0, "quantity": 10.0 },
      "approved": {
        "price": 0,
        "quantity": 0,
        "action_code": "29",
        "action_message": "Quantity exceeds purchase quantity"
      }
    }
  ],
  "refund_errors": [
    {
      "code": "item_refund_quantity_exceeds_original_quantity",
      "message": "Attempted to refund a greater quantity of an item than originally purchased",
      "source": {
        "resource": "Payments",
        "ref": "{payment_ref}"
      }
    }
  ]
}

WIC Transaction Error Handling Rules

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 is not possible (e.g., a different subcategory is needed), refund without substitute_with and prompt the customer for a new balance_session to purchase the substitute separately.


Transaction Limits

  • Items per transaction: 50 unique UPC/PLUs max
  • Cards per transaction: 1 WIC card only
  • Minimum purchase: None (cannot require minimums)
  • Taxes on WIC items: Exempt

Timing Constraints

  • Balance session validity: Until expires_at (a configurable value, typically less than 24 hours)
  • Payment capture: Must occur before expires_at
  • Order modifications: Until delivery; no refunds after the customer receives items
  • Benefit expiration: Benefits cannot be used after benefit_end_date

Receipt Data Requirements

WIC requires specific data elements at multiple points in the customer's journey. Each column below maps to a distinct receipt moment. For how Forage structures and delivers receipt objects, see receipt requirements.

Data ElementBalance InquiryWIC ConfirmationVendor Sales Receipt
PAN (Last 4 digits)
Store Name/Address
Date/Time
WIC Benefit Expiration
WIC Items Purchased✅ (Proposed)✅ (Final)
Category Description
Sub-Category Description
Item Name
Remaining Balance
Discounts Applied

Receipt types:

  • Beginning WIC Balance — Generated at balance inquiry completion
  • Real-Time Shopping WIC Confirmation — Updated as the customer adds items to their cart
  • Pre-Purchase WIC Confirmation — Shown at checkout before payment
  • Tentative WIC Sales Receipt — Generated post-checkout, pre-fulfillment; still subject to order modifications
  • Finalized WIC Sales Receipt — Generated post-fulfillment; the WIC order cannot be modified after this point

Pricing Rules: NTE and Split-Tender

NTE (Not-to-Exceed) Adjustments

Some items have maximum prices set by the state. If your submitted price exceeds the NTE limit, Forage automatically adjusts the approved amount down. The response includes the adjusted price in approved.price with action_code: "26". Use amount from the response for settlement, not the originally requested price.

Split-Tender

  • CVB items only: If the purchase exceeds the CVB dollar balance, the customer pays the difference with another payment method.
  • Non-CVB items: No split-tender. The item is either fully covered by WIC or not covered at all.

Discount Calculation Rules

Apply discounts first, then transact WIC. When sending requests to Forage, set price for each product to the net amount after discount.

Who Benefits from Discounts?

Item TypeWho Saves Money
Non-CVB items (milk, eggs, etc.)The state agency (lower reimbursement)
CVB items (fruits & vegetables)The participant (stretches their dollar benefit further)

See Integrate WIC with Forage: Apply Discounts to WIC Transactions for worked examples of all discount scenarios.