DEV Community

Cover image for A Valid Payment Webhook Can Arrive After a Full Refund
HaoJie Luo (Fone)
HaoJie Luo (Fone)

Posted on AI-assisted

A Valid Payment Webhook Can Arrive After a Full Refund

Your webhook endpoint receives a paid event. The signature passes. Meanwhile, the order has already been fully refunded.

That can happen when notification delivery is delayed. Before granting access, the receiver has to compare the event with the merchant's purchase and read the current order state.

Here's a small offline exercise that makes the distinction visible. It uses a synthetic order, a public test secret and a fixed clock. You can run it with Python's standard library.

Verify the event, then change the current order

Download the exercise and extract it into an empty directory. Run this from that directory.

from exercise import verify, eligible
from test_exercise import signed, SECRET, EXPECTED, ORDER

raw, headers = signed()
event = verify(raw, headers, SECRET, now=1000)
refunded_order = dict(
    ORDER, revision='8', refund_status='full', refunded=ORDER['amount']
)
print(event['id'])
print(eligible(event, EXPECTED, refunded_order, local_revision='8'))
Enter fullscreen mode Exit fullscreen mode

This prints the event ID and the delivery decision.

sample-event
False
Enter fullscreen mode Exit fullscreen mode

The verifier accepted the signed event and returned its ID. The eligibility check then rejected delivery because the current order records a full refund.

The event keeps revision 7. The refunded order is revision 8. Replacing the old payment event with current order state would lose useful history. The receiver can keep both and use the newer state when deciding whether to grant access.

Payment webhook checks from original request bytes through event validation, local order matching and current fulfillment eligibility

Check the failure modes separately

Run the included suite from the same extracted directory:

python3 -m unittest -v
Enter fullscreen mode Exit fullscreen mode

It contains 12 tests. They cover changed signed bytes, timestamp boundaries, event identity, a correctly signed wrong amount, a different chain, a newer refund state and repeated verification of the same event.

These tests help distinguish two questions. Did the sender sign these exact bytes? Does the merchant currently have an eligible purchase to fulfill?

For example, the fixture uses a 300-second timestamp window. Replaying the same delivery inside that window still passes verification. The receiver needs persistent duplicate handling as well as a signature check.

Current-state reads also need a recovery rule. If an order API returns a snapshot older than the event, this exercise raises an error so the caller can retry. Choosing how to persist that pending work is part of the receiver design.

The download implements selected signature and order checks over synthetic data. An HTTP integration also needs raw-body capture, complete schema validation, durable event storage and a worker that can recover after failure.

The full walkthrough follows these checks through the merchant workflow and includes an acceptance worksheet.

This adaptation was prepared with AI assistance. The downloadable exercise's 12 tests and the example above were rerun while preparing it.

Top comments (0)