The most common mistake in payment integration: assuming the payment status is known the moment the customer taps 'pay'. In reality the payment is async — its status is only certain when the provider confirms via webhook. This is what I learned integrating Midtrans QRIS into Dexova's POS.
The webhook is the source of truth, not the frontend response
A frontend that shows 'payment successful' based on the immediate response is a bug waiting to happen. The customer can close the tab, the signal can drop, and the payment still settles a few seconds later.
Truth comes from the webhook (server-to-server notification) that flips the transaction status from pending to settled. The frontend simply displays the recorded status, not a guess.
Verify the signature, always
A webhook endpoint is public: anyone can POST pretending to be Midtrans. So every notification must have its signature (signature key) verified before it is trusted. Notifications that fail verification are dropped without changing anything.
This is not optional. Accepting a payment status without verification means letting anyone mark an order as 'paid' for free.
Idempotency: the same notification may arrive twice
Payment providers guarantee at-least-once, not exactly-once: the same webhook can be resent. The handler must be idempotent — processing the second notification must not double the payment or trigger duplicate effects.
In practice: lock the transaction by order ID, check the current status, and only perform valid transitions. A daily reconciliation against the Midtrans dashboard closes the gap if a webhook is missed entirely.
Summary
Correct payment integration centers on three things: treat the webhook as truth (not the frontend response), verify every signature, and make the handler idempotent. Add reconciliation to sleep well.