Handle redemption / deliver
After a buyer commits, they redeem the voucher when they're ready to receive the goods. This emits VoucherRedeemed on-chain and transitions the exchange from COMMITTED to REDEEMED. This event is what you wire to your fulfillment system.
For the conceptual model, see Fulfillment channels and Eventing & indexing.
Listen for the event
import { ethers } from "ethers"
import exchangeAbi from "@bosonprotocol/common/dist/cjs/abis/IBosonExchangeHandler.json"
const diamond = new ethers.Contract(DIAMOND_ADDRESS, exchangeAbi, provider)
diamond.on("VoucherRedeemed", async (offerId, exchangeId, executedBy, event) => {
if (await alreadyDelivered(exchangeId)) return
await deliver(exchangeId)
await markDelivered(exchangeId)
})After delivery: complete the exchange
Once delivered, you (or anyone) can complete the exchange after the dispute window closes. This releases the seller's earnings into the treasury.
await (await sdk.completeExchange(exchangeId)).wait()
// or batch:
await (await sdk.completeExchangeBatch([eId1, eId2, eId3])).wait()Common patterns
Pattern: Synchronous delivery
For digital goods that can be delivered instantly, use atomic commit-and-redeem. The buyer commits and redeems in one transaction, and your server returns the goods in the HTTP response. See Buyers → Atomic commit-and-redeem and Quickstart → x402 server.
Pattern: Asynchronous delivery (physical goods)
Listen to VoucherRedeemed, kick off your shipping flow, mark delivered = true keyed by exchangeId. Schedule completeExchange to run after the dispute window closes.
Pattern: Buyer initiates fulfillment via webhook
At commit time, your widget/SDK captures a delivery address or contact. Post it to your backend keyed on exchangeId. On redemption, you have all the info you need to deliver.
Common gotchas
- Make delivery idempotent. Reorgs and re-fired webhooks happen. Key on
exchangeIdor(txHash, logIndex). - Subgraph polling can miss redemptions during indexer downtime. Prefer RPC events for low-latency triggers; use the subgraph for catch-up.
- Don't auto-complete inside the dispute window. You can call
completeExchange, but doing so before the window closes is a no-op and wastes gas.