Process EBT SNAP refunds
Learn how to process partial refunds for online SNAP and EBT payments, including FNS requirements and developer implementation options.
A refund is a repayment to a customer after they return item(s) in an order. This guide provides detailed information on refunding EBT SNAP benefits.
The guide is divided into the following sections:
Refund Receipt Details
For details on refund receipts, see the Forage receipts guide.
Refund scenarios
Item substitutions
An item substitution is when a lower-cost alternative replaces an item in the original order. A customer might initiate the substitution, or a retailer or delivery shopper might make the swap when they discover a price discrepancy, an inventory error, or that the item is out of stock. Weighted items can also result in item substitutions. If the fulfilled weight is lower than the ordered weight, then the difference may result in a refund.
Item removals
An item removal is when an item is removed from the order. A customer might remove it while the order is being fulfilled, or a retailer or delivery shopper might opt to remove an item if it’s out of stock and there’s no adequate substitute, or if the retailer doesn’t support substitutions.
Item returns
A customer might initiate a return if an item has expired or is otherwise unsatisfactory.
Promotions And Refunds
Refunds may affect eligibility for promotions, such as free shipping. To maintain a good customer experience, consider honoring the original offer.
FNS requirements
Deadlines
- Customer-requested refunds must be processed within two business days of receiving the returned item.
- Refunds due to merchant error, such as overestimated weighted products, out-of-stock items, or substitutions, must be processed on the same day as the original order was fulfilled.
- Merchants must send customers a refund notification, whether via email, a confirmation screen, or the customer’s account portal, within 24 hours of the refund being completed.
Refund Receipt Requirements
For details and examples of refund notifications, see the Forage receipts guide.
Payment instruments
FNS Refund Requirements Summary
Refunds must match original payment methods:
- SNAP-ineligible items can’t be refunded to SNAP.
- EBT Cash–ineligible items can’t be refunded to EBT Cash.
- Never refund more than was originally charged.
SNAP
Only SNAP-eligible purchases can be refunded to a customer's SNAP balance. To protect against fraud, no other payment instrument can be used to receive the SNAP refund. Refunds from SNAP to credit/debit, EBT Cash, or store gift cards are prohibited.
Refunding non-SNAP items, like alcohol, should never result in a SNAP refund.
EBT Cash
Only EBT Cash-eligible purchases can be refunded to a customer's EBT Cash balance. To protect against fraud, no other payment instrument can be used to receive the EBT Cash refund. Refunds from EBT Cash to credit/debit, SNAP, or store gift cards are prohibited.
Refunding non-EBT Cash items, like alcohol, should never result in an EBT Cash refund.

SNAP purchases can only be refunded to SNAP. EBT Cash purchases can only be refunded to EBT Cash.
Refund entry
Every retailer must have a secure method for authorized employees to manually enter refund requests using password-protected user IDs. There must be functionality for an employee to either issue refunds by item or to directly enter a specific amount to be refunded.
Refund notification
Refer to the EBT SNAP refunds section of the receipts guide for a comprehensive list of information that must be shared with customers after a refund is completed.
How to process online EBT SNAP refunds
When a full order is refunded, the merchant returns the total amount paid by the customer. However, if only some items are refunded, then the merchant refunds just the cost of those specific items.
To illustrate the different options, consider the following example receipt. On this receipt, "SP" indicates that an item is SNAP-eligible. "EBT" indicates EBT Cash eligible.

The receipt illustrates an order involving multiple payment instruments.
Imagine the customer returns Item A, a $10.00
SNAP-eligible item.
- The refunds flow that restores the original tender amount refunds
$10.00
to the customer’s SNAP balance. - The flow that maximizes the credit/debit refund refunds
$10.10
to the customer's credit card. - The flow that automatically maximizes SNAP uses the
\refund_by_product
endpoint in Fully Hosted Checkout and returns the optimal split without manual redistribution.
Therefore, to handle partial refunds, select one of these refund type methods:
- Restore the original tender amount
- Manually maximize the credit/debit return
- Automatically maximize SNAP (Fully Hosted Checkout only)
👉 Or, you can jump to the example API requests.
Method 1. Restore the original tender amount
If you map the exact tender amount paid for each item in your database, the quickest refund flow is to issue a refund based on the original payment split.
In this scenario, the following might occur:
- If the customer returns Item A, a refund of
$10.00
is issued to the customer’s SNAP balance. - If the customer returns both Item A and Item B, a refund of
$10.00
is applied to the credit card balance. - If the customer returns Item D, a refund of
$5.05
is applied to the customer’s EBT Cash account.
Method 2. Manually maximize the credit/debit return
To maximize the credit/debit return instead, you can redistribute the payment split.
Calculate the refund based on whether the restored EBT SNAP balance can be used for other eligible items in the cart. This step isn't mandatory, but it offers a better customer experience.
Not required by FNS, but improves CX
This refund flow is optional for compliance but recommended for customer experience teams.
If you're using Fully Hosted Checkout, the refund_by_product
endpoint automatically maximizes SNAP refunds.
When a customer pays for SNAP-eligible items using multiple payment methods, it's possible to redistribute the refund split. The final refunded amounts are calculated based on whether the restored SNAP balance can be applied to other eligible items in the order.
For example, items with higher tax rates can be refunded to SNAP first, reducing out-of-pocket taxes and increasing the customer’s credit/debit refund.
We've broken this into five steps below to show how to implement this logic manually.
Step 1: Calculate the updated order total
Subtract the cost of the returned item from the total of the original order.
In this scenario, you would subtract the $10.00
value of Item A from the $60.00
order total. This gives an updated order total of $50.00
.
Step 2: Sort any remaining SNAP-eligible items by the highest tax rate
If SNAP-paid items are returned, the SNAP credit can be redistributed to remaining SNAP-eligible order items. To maximize a customer’s potential credit/debit card return, apply the SNAP credit to the highest-taxed items first.
In this scenario, after removing Item A, two SNAP-eligible items are in the cart:
- Item C:
$10.00
, Tax rate 1% - Item B:
$10.00
, Tax rate 0%
Item C has a higher tax rate, so it’s sorted above Item B.
Step 3: Apply SNAP credit to SNAP-eligible items
Apply all of the returned SNAP credit to the remaining SNAP-eligible items in the cart, starting with the highest-taxed items.
If the credit exceeds the value of the remaining SNAP-eligible items, then return the excess SNAP amount to the customer’s SNAP balance.
In this scenario, returning Item A restores $10.00
in SNAP credit. That $10.00
is fully applied to Item C because its tax rate is higher than that of Item B.
Step 4: Distribute the original EBT Cash charge
The original EBT Cash payment can be used for any remaining items that are SNAP-eligible or EBT Cash-eligible. The order of the items doesn’t matter: unlike SNAP purchases, EBT Cash purchases are taxed.
If the original EBT Cash payment exceeds the cost of the remaining eligible items, refund the difference to the customer’s EBT Cash account balance.
In this scenario, the customer paid $5.05
using EBT Cash. That $5.05
is fully applied to the original Item D.
Step 5: Apply the original credit/debit card payment to outstanding items and fees
Apply the original credit/debit card payment to any outstanding items and fees.
If the payment exceeds the cost of the outstanding items, refund the difference to the customer’s credit/debit card. If the cost of the outstanding items exceeds the original payment, create a new order and charge the customer the remaining amount.
In this scenario, the following items remain in the cart:
- Item B:
$10.00
, Tax rate 0% - Item E:
$25.00
(plus 1% Tax rate:$25.25
)
The original $45.35
credit/debit card payment is applied to the outstanding balance of $35.25
. The remaining $10.10
will be refunded to the credit/debit card.
Method 3. Automatically maximize SNAP (Fully Hosted Checkout only)
If you're using Fully Hosted Checkout, the refund_by_product
endpoint automatically uses the maximize-SNAP logic. This means you don't have to manually perform the redistribution steps (Method 2, above).
Important
This automatic behavior is available only on the
refund_by_product
endpoint. Other refund endpoints will not automatically maximize SNAP.
For details and request/response examples, see the Refund specific products in an Order section below.
Query the Forage API
The endpoints that you need to call to issue refunds differ depending on whether you’ve built with an SDK or the Forage Checkout UI. SDK integrations create PaymentRefunds
, while Fully Hosted and Custom integrations create OrderRefunds
.
How to handle a failed refund
Regardless of your integration type, if a refund attempt fails, retry the request with the same payload and the same idempotency key.
SDK
SDK integrations call the Payment Refunds endpoints.
To refund a payment, send a POST
to /payments/{payment_ref}/refunds/
, passing the ref
for the relevant Payment to be refunded as the path param. In the request body, specify the amount
to be refunded, along with a reason
, as shown in the following example.
Request
curl --request POST \
--url https://api.sandbox.joinforage.app/api/payments/{payment_ref}/refunds/ \
--header 'Authorization: Bearer <authentication-token>' \
--header 'Idempotency-Key: <idempotency-key>' \
--header 'Merchant-Account: <merchant-account>' \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '
{
"amount": 25.99,
"reason": "Order could not be delivered"
}
'
Response
{
"ref": "0ddbcda871",
"payment_ref": "b873fe62dc",
"merchant": "9000055",
"funding_type": "ebt_snap",
"amount": "25.99",
"reason": "Order could not be delivered.",
"created": "2023-09-18T11:38:29.279185-07:00",
"updated": "2023-09-18T11:38:29.279209-07:00",
"status": "requires_confirmation",
"last_processing_error": null,
"receipt": null
}
For more information, see these API reference docs:
Forage Checkout (Fully Hosted and Custom)
Forage Checkout integrations, for both Fully Hosted and Custom, call the Order Refunds endpoints.
Call different endpoints to refund an entire Order or refund part of an Order.
Refund an entire Order
To refund an entire Order, send a POST
to /orders/{order_ref}/refunds/
, passing the ref
for the relevant Order to be refunded as the path param. In the request body, specify the reason
for the refund and a metadata
object, as in the following example.
Request
curl --request POST \
--url https://api.sandbox.joinforage.app/api/orders/{order_ref}/refund_all/ \
--header 'Authorization: Bearer <authentication-token>' \
--header 'Idempotency-Key: <idempotency-key>' \
--header 'Merchant-Account: <fns-number>' \
--header 'accept: application/json' \
--header 'content-type: application/json'
--data '
{
"reason": "Order could not be delivered",
"metadata": {}
}
'
Response
{
"ref": "e0f7607d5f",
"snap_total": "10.00",
"ebt_cash_total": "9.00",
"remaining_total": "0.00",
"product_list": [],
"status": "draft",
"delivery_address": {
"city": "San Francisco",
"country": "US",
"line1": "1856 Market St.",
"line2": "Apt. 3",
"state": "CA",
"zipcode": "94105,"
},
"is_delivery": false,
"success_redirect_url": "",
"cancel_redirect_url": "",
"supported_benefits": [
"snap",
"ebt_cash",
"non_ebt"
],
"success_date": null,
"receipt": null,
"customer_id": null,
"is_commercial_shipping": null,
"expires_at": "2023-09-29T18:33:28.315051Z",
"psp_customer_id": null,
"external_order_id": null,
"payments": [],
"refunds": []
}
For more information, see these API reference docs:
Refund part of an Order
To refund part of an Order, specifically part or all of a single OrderPayment associated with an Order, send a POST
to /orders/{order_ref}/refunds/
, passing the ref
for the relevant Order to be refunded as the path param. In the request body set the amount
to be refunded, the ref
for the relevant OrderPayment, the reason
for the refund, and a metadata
object, as in the following example.
Request
curl --request POST \
--url https://api.sandbox.joinforage.app/api/orders/{order_ref}/refunds/ \
--header 'Authorization: Bearer <authentication-token>' \
--header 'Idempotency-Key: 123e4567e8' \
--header 'Merchant-Account: <fns-number>' \
--header 'accept: application/json' \
--header 'content-type: application/json'
--data '
{
"amount": 25.99,
"payment": <payment-ref>,
"reason": "Order could not be delivered",
"metadata": {}
}
'
Response
{
"ref": "1ccabfg754",
"order": "e0f7607d5f",
"payment": "f587edf124",
"merchant": "9000055",
"funding_type": "ebt_snap",
"amount": "20.00",
"merchant_fixed_settlement": 5.95,
"platform_fixed_settlement": 5.11,
"reason": "Order could not be delivered.",
"metadata": {},
"created": "2023-09-18T11:38:29.279185-07:00",
"updated": "2023-09-18T11:38:29.279209-07:00",
"status": "requires_confirmation",
"last_processing_error": null,
"receipt": null,
"tpp_lookup_id": "re_3JemUSGfBYJeLEva0af6My64",
"refund_errors": []
}
Refund specific products in an Order
You can now refund individual products within an Order using the POST /orders/{order_ref}/refund_by_product/
endpoint.
This is useful when a refund applies to only certain items, rather than the entire order or a specific payment amount.
Server-side only
For security, all requests to this endpoint must be made from your server. Do not call this endpoint from client-side code.
Request
Send a POST
to /orders/{order_ref}/refund_by_product/
, passing the order_ref
of the relevant Order as the path parameter.
The request body must include:
products[]
– A list of the products that need to be returned:id
(number) – A unique identifier for the product.quantity
(number) – The number of the items of the product to be returned.
reason
(string) – A string that describes why the refund is happening.metadata
(object) – Merchant-defined key–value pairs for additional context. Pass{}
if not applicable.
Example:
curl --request POST \
--url https://api.sandbox.joinforage.app/api/orders/{order_ref}/refund_by_product/ \
--header 'Authorization: Bearer <authentication-token>' \
--header 'Idempotency-Key: <idempotency-key>' \
--header 'Merchant-Account: <fns-number>' \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '
{
"products": [
{
"id": 1235,
"quantity": 2
}
],
"reason": "Product is damaged",
"metadata": {}
}
'
Response
The response returns an array of refund objects, with each refund containing the following key details:
Object | Description |
---|---|
ref | A unique reference hash identifying this OrderRefund . |
order | The reference hash of the parent Order being refunded. |
payment | The reference hash of the specific OrderPayment being refunded. |
merchant | The merchant’s identifier, such as their FNS number or a Forage-provided merchant ID. |
funding_type | The tender type for the refund; e.g. benefit , credit_tpp , ebt_snap , or ebt_cash . |
amount | The refund amount in USD (decimal). |
merchant_fixed_settlement | Fixed USD amount restored to the merchant for EBT Cash refunds, before fees. |
platform_fixed_settlement | Fixed USD amount restored to the platform for EBT Cash refunds, before fees. |
reason | A string explaining why the refund is being issued. |
metadata | Optional merchant-defined key–value pairs for storing extra information. |
created | ISO 8601 timestamp when the refund was created. |
updated | ISO 8601 timestamp when the refund was last updated. |
status | Current refund status: canceled , failed , processing , or succeeded . |
last_processing_error | An object with code and message describing the latest Payments API error, or null . |
receipt | Refund receipt object for customer display; initially null until available. |
tpp_lookup_id | External ID from the third-party processor (e.g. Stripe Refund ID); null for EBT. |
previous_errors | An array of errors returned during refund creation; not present in GET responses. |
Example:
{
"ref": "1ccabfg754",
"order": "e0f7607d5f",
"payment": "f587edf124",
"merchant": "9000055",
"funding_type": "ebt_snap",
"amount": "20.00",
"merchant_fixed_settlement": 5.95,
"platform_fixed_settlement": 5.11,
"reason": "Order could not be delivered.",
"metadata": {},
"created": "2023-09-18T11:38:29.279185-07:00",
"updated": "2023-09-18T11:38:29.279209-07:00",
"status": "requires_confirmation",
"last_processing_error": null,
"receipt": null,
"tpp_lookup_id": "re_3JemUSGfBYJeLEva0af6My64",
"previous_errors": []
}
Refund Status Updates
Use the
REFUND_STATUS_UPDATED
webhook to track changes in refund statuses.
For more information, see these API reference docs:
- Refund part of an order
- Create refunds for specific products in an Order
- Order Refunds
What’s Next
Tell your users what they should do after they've finished this page
Updated 8 days ago