Track Toast refunds as distinct negative adjustments keyed to the original order so net sales stay accurate even when webhooks retry or arrive out of order.

Subscribe to Toast order and refund events separately

Toast sends order webhooks on creation and status changes, but refunds arrive through a distinct refund event or as an adjustment on the order object. Register for both the orders and refunds topics in the Toast developer portal so your endpoint receives each type without relying on polling the orders endpoint after the fact.

When a refund webhook arrives, the payload includes the original order GUID and a refund GUID. Store the refund GUID in its own table with a foreign key to the order rather than mutating the order record directly. This separation prevents an order update webhook from overwriting a refund that was applied moments earlier.

Use refund GUIDs for idempotent inserts

Before writing a refund row, run a SELECT on the refund GUID. If the row already exists, drop the event. Toast webhooks can be delivered more than once during retries or when the merchant re-issues the same refund through the POS. A simple unique constraint on the refund GUID column enforces this check at the database level.

When the order itself is updated after a refund, Toast may resend the full order object. Compare the order’s refund array against your stored refund GUIDs and skip any that already exist. This keeps the order snapshot intact while still recording the refund once.

Reconcile net sales from two tables

Maintain one table for order totals and a second for refund amounts. When you need daily or weekly sales, sum order totals and subtract the sum of refunds that fall inside the same date range. Joining on order GUID lets you attribute each refund to its source order without altering the original order amount.

Add a processed_at timestamp on both tables. When a webhook retry arrives after the first successful write, the existing processed_at value tells you the adjustment has already been counted. This pattern also surfaces late refunds that land in a later reporting period than the original sale.

Handle retries and partial failures with circuit checks

Wrap the refund handler in the same retry logic you use for order ingestion, but add an explicit cache lookup for the refund GUID before any external call. If the GUID is present, return 200 immediately so Toast stops retrying. This avoids repeated database writes during transient downstream outages.

If the downstream ledger write fails after the refund row is inserted, mark the row as pending and surface it in an admin queue. On the next successful ledger sync, reprocess only the pending rows rather than re-ingesting the entire webhook stream. This keeps the failure surface small and prevents refund amounts from being applied twice when the ledger recovers.