DEV Community

EcomTrade24 Pay
EcomTrade24 Pay

Posted on

Your Checkout Redirect Is Not Payment Confirmation


One of the most common mistakes in ecommerce payment integrations is also one of the easiest to miss.

The customer pays, gets redirected to a success page, and the shop marks the order as paid.

It feels logical.

But it is not reliable.

A checkout redirect is not payment confirmation.

It only tells you that the customer's browser reached a certain URL.

That is very different from your backend receiving a trusted payment event.

In real ecommerce, this difference matters a lot.

The redirect is only part of the customer experience

A redirect is useful.

It helps the customer know what happened after checkout.

For example:

/payment/success
/payment/cancel
/order/thank-you
Enter fullscreen mode Exit fullscreen mode

Those pages are good for user experience.

But they should not be the only thing that decides whether an order is paid.

Why?

Because browsers are unreliable.

A customer can close the tab.

A phone connection can drop.

A browser extension can block something.

The redirect can be delayed.

The customer can refresh the page.

The success page can be opened more than once.

A user can manually visit a URL if the logic is weak.

That does not mean the payment was confirmed.

It only means the page was visited.

Payment confirmation belongs on the backend

The backend needs a trusted event from the payment system.

That is usually a webhook.

A cleaner flow looks like this:

Customer starts checkout
Backend creates payment session
Customer is redirected to hosted checkout
Payment provider processes payment
Payment provider sends webhook to merchant backend
Backend verifies webhook
Backend updates order status
Customer sees final status
Enter fullscreen mode Exit fullscreen mode

The important part is this:

Webhook received + verified = backend payment confirmation
Enter fullscreen mode Exit fullscreen mode

The success page should not be the source of truth.

The webhook should be.

This applies to every ecommerce stack

This is not only a WooCommerce problem.

The same rule applies to:

  • Shopify apps
  • Custom PHP shops
  • Laravel stores
  • Next.js storefronts
  • Node.js backends
  • Headless ecommerce
  • Marketplace platforms
  • Digital product platforms
  • SaaS checkout systems
  • Mobile app backends

The frontend can guide the customer.

The backend must confirm the money.

A simple bad pattern

This is the risky version:

if current_url == "/payment/success":
    mark_order_as_paid()
Enter fullscreen mode Exit fullscreen mode

This is weak because the decision depends on a browser route.

A better version:

on_webhook_received(event):
    verify_signature(event)

    if event.status == "paid":
        mark_order_as_paid(event.order_id)
Enter fullscreen mode Exit fullscreen mode

The customer redirect can still show a nice page.

But the order should only become paid after a trusted backend event.

Webhooks can arrive before or after the redirect

A common reason developers get confused is timing.

Sometimes this happens:

Payment completed
Webhook arrives
Order marked paid
Customer redirects to success page
Enter fullscreen mode Exit fullscreen mode

Other times this happens:

Payment completed
Customer redirects to success page
Webhook arrives later
Order marked paid later
Enter fullscreen mode Exit fullscreen mode

Both are normal.

So the success page should handle both cases.

For example:

Payment received. We are confirming your order.
Enter fullscreen mode Exit fullscreen mode

Then the page can poll the backend or show a pending confirmation state.

The mistake is assuming the redirect and webhook will always happen in the same order.

They will not.

Use clear internal statuses

A good payment integration needs more than paid and failed.

Real payment sessions have more states.

For example:

created
pending
processing
paid
failed
expired
cancelled
review
refunded
Enter fullscreen mode Exit fullscreen mode

This gives your system more control.

A checkout can be created but not paid.

A payment can be pending.

A provider can request review.

A session can expire.

A customer can cancel.

A refund can happen later.

If your system only understands success or failure, you will lose important information.

Do not trust the frontend amount

Another serious mistake is trusting payment data from the frontend.

Bad idea:

{
  "amount": 49.99,
  "currency": "EUR",
  "order_id": "123"
}
Enter fullscreen mode Exit fullscreen mode

If this data comes directly from the browser and your backend does not verify it, you have a problem.

The backend should calculate or load the real order amount from the database.

Better flow:

Frontend sends order_id
Backend loads order from database
Backend calculates amount
Backend creates payment session
Provider returns checkout URL
Frontend redirects customer
Enter fullscreen mode Exit fullscreen mode

The browser should not decide the final price.

The backend should.

Verify the webhook

A webhook endpoint should not blindly accept requests.

At minimum, think about:

  • Signature verification
  • API key or token validation
  • Expected event type
  • Matching payment session ID
  • Matching amount and currency
  • Duplicate event handling
  • Safe logging
  • Internal status mapping

A webhook endpoint is a public URL.

Treat it like one.

Bad pattern:

on_webhook_received(request):
    mark_order_as_paid(request.order_id)
Enter fullscreen mode Exit fullscreen mode

Better pattern:

on_webhook_received(request):
    verify_signature(request)
    event = parse_event(request)

    payment = find_payment(event.payment_id)

    if payment.already_processed:
        return success_response()

    if event.amount != payment.expected_amount:
        flag_for_review()
        return success_response()

    if event.currency != payment.expected_currency:
        flag_for_review()
        return success_response()

    update_payment_status(event.status)
    update_order_status(payment.order_id)
    mark_event_processed(event.id)

    return success_response()
Enter fullscreen mode Exit fullscreen mode

The exact code depends on your stack, but the principle stays the same.

Verify first.

Update second.

Log everything.

Make webhook processing idempotent

Payment providers may send the same webhook more than once.

That is normal.

Your system must handle duplicates safely.

A duplicate webhook should not create duplicate wallet credits, duplicate invoices, duplicate licenses, duplicate subscription periods or duplicate order fulfillment.

Use something like:

event_id
payment_id
order_id
processed_at
status_before
status_after
Enter fullscreen mode Exit fullscreen mode

Before applying a webhook event, check whether it was already processed.

if event_id_already_processed(event.id):
    return success_response()
Enter fullscreen mode Exit fullscreen mode

This one rule prevents many ugly bugs.

Return success after safe handling

If your webhook endpoint receives a valid event and processes it safely, return a success response.

If you return an error after processing, the provider may retry.

That can be okay if your system is idempotent, but it creates noise.

A clean webhook handler should:

Receive event
Verify event
Process safely
Store event
Return success
Enter fullscreen mode Exit fullscreen mode

If something is suspicious, you can still store it and flag it for review.

Do not lose the event.

Your customer page should be honest

The customer success page should not lie.

If the backend has already confirmed the payment, show:

Payment confirmed. Your order is now being processed.
Enter fullscreen mode Exit fullscreen mode

If the webhook has not arrived yet, show:

Payment received. We are confirming the final status.
Enter fullscreen mode Exit fullscreen mode

If the customer cancelled, show:

Payment was not completed. You can try again or choose another method.
Enter fullscreen mode Exit fullscreen mode

If the session expired, show:

This payment session has expired. Please create a new checkout.
Enter fullscreen mode Exit fullscreen mode

Clear status messages reduce support tickets.

Logs are part of the product

Payment logs are not just developer tools.

They help merchants, support teams and finance teams understand what happened.

A useful payment log should show:

Checkout session created
Customer redirected
Provider selected
Payment method selected
Webhook received
Status changed from pending to paid
Order updated
Customer redirect completed
Enter fullscreen mode Exit fullscreen mode

When something fails, the log should also show the reason if available.

Without logs, support becomes guesswork.

And with payments, guesswork is expensive.

Payment routing makes this more important

If your system supports more than one provider or method, clean status handling becomes even more important.

A payment may be routed to:

  • Card
  • Bank transfer
  • Wallet
  • Crypto
  • Local payment method
  • Manual payment link
  • Fallback provider

Each route may have its own provider status names.

Your internal system should normalize them.

Example:

Provider A: completed  -> internal: paid
Provider B: success    -> internal: paid
Provider C: settled    -> internal: paid
Provider D: confirmed  -> internal: paid
Enter fullscreen mode Exit fullscreen mode

This keeps your merchant dashboard and shop integration clean.

The merchant should not need to understand every provider's internal language.

A practical checklist

Before your payment integration goes live, ask:

  • Can the backend create the payment session?
  • Is the amount loaded from the database, not trusted from the browser?
  • Is the customer redirected to a secure hosted checkout?
  • Does the backend receive webhooks?
  • Are webhooks verified?
  • Are webhook events idempotent?
  • Are amount and currency checked?
  • Are internal statuses normalized?
  • Can the order be pending while confirmation is still processing?
  • Can the system handle expired sessions?
  • Can the customer retry payment?
  • Can support see a useful payment log?
  • Can the merchant understand what happened without reading server logs?

If not, the integration is not finished.

It only works when everything goes perfectly.

Production ecommerce is not perfect.

Final thought

A success page is not a payment confirmation.

It is only part of the customer journey.

The real confirmation should happen on the backend through verified webhook events, safe status handling and clear order updates.

That is true whether you are building for WooCommerce, Shopify, Laravel, Next.js, a custom PHP shop or a headless ecommerce system.

A good checkout should feel simple to the customer.

But behind the scenes, the system needs to be strict.

Confirm the payment on the backend.

Keep the redirect for the customer experience.

Log what happened.

Handle retries safely.

Never build a payment system that only works on the happy path.

EcomTrade24 Pay is built around hosted checkout, payment links, API/webhook automation and Smart Routing for merchants who need more than one fragile payment path.

https://pay.ecomtrade24.com

Top comments (0)