ServicesAI StudioIndustriesPricingInsightsHow we workAboutTeamFAQAdvisorCost calculatorContactGet a quote →

How to accept GCash on your website

Four routes, what each costs you in fees and in engineering, and the reconciliation problem that turns a working checkout into a bookkeeping mess six months later.


Short answer

There are four ways to take GCash on a website. A payment gateway — PayMongo, Xendit, Maya Business, Dragonpay — is the right answer for almost everyone: one integration, GCash alongside cards and Maya, and webhooks that tell your system what happened. A platform plugin on Shopify or WooCommerce is the same thing with less code. QR Ph suits in-person and low-volume selling. Manually posting a GCash number works until roughly thirty orders a month and then quietly costs more in reconciliation time than any gateway fee. Expect gateway fees in the low single digits of each transaction, and check the current published rate — they change.

Where we stand, since it is relevant

We build GCash checkouts for clients and we do not accept GCash ourselves — our own invoices are paid by card, PayPal or bank transfer. That is not a verdict on GCash. It is that we invoice businesses in the hundreds of thousands of pesos, which is the case GCash fits worst, and a retail store selling ₱500 items is the case it fits best. Take the recommendation below on its reasoning, not on what we happen to use.

The four routes

RouteEngineeringFeesBest for
Payment gateway
PayMongo, Xendit, Maya Business, Dragonpay
API + webhook handlingLow single-digit % per transactionAlmost everyone with a real order volume
Platform plugin
Shopify or WooCommerce app
Configuration, little codeGateway fee, sometimes plus app feeStores already on a platform
QR Ph
The BSP interoperable standard
MinimalLower, varies by acquirerIn person, events, low volume
Manual number or QRNoneNo fee, high labourUnder ~30 orders a month, and not for long

Do not choose on headline fee. The manual route has the lowest fee and by far the highest real cost, because someone is matching screenshots to orders by hand — and that person makes mistakes that surface as an angry customer whose paid order was never marked paid.

The part that actually breaks: reconciliation

Taking the money is the easy half. Knowing reliably which order it belongs to, exactly once, is where these integrations fail — usually months after launch, quietly, and in the direction of your books rather than your checkout.

Verify the signature on the raw body

Every gateway signs its webhooks, and the signature covers the exact bytes sent. If your framework parses the JSON before you verify, you have already lost the bytes and verification becomes impossible — or worse, appears to pass against a re-serialised body that differs by a space. Read the raw body first, verify, then parse. An unverified payment webhook is an open endpoint that marks orders paid on request.

Make replays harmless, not merely unlikely

Gateways retry. A webhook that times out, or that your host drops during a deploy, will arrive again — sometimes minutes later, sometimes twice at once. If your handler adds a payment row each time it runs, you will eventually record the same payment twice and reconcile to a number nobody can explain.

Do not solve this by checking whether the row exists and then inserting it: two concurrent deliveries both check, both find nothing, and both insert. Put a unique constraint on the gateway’s own event identifier and let the database settle the race. One writer wins, the other is rejected, and the rejection is the correct outcome rather than an error to report.

Expect two callers, not one

Most checkouts have two paths to the same news: the customer is redirected back to your site after paying, and the gateway sends a webhook. Both are legitimate, both can arrive first, and either can be the only one that arrives — the customer may close the tab, and the webhook may be delayed. Handle both, deduplicate on the same key, and make the second one a no-op. Sending two receipts for one payment is a support ticket you wrote yourself.

The failure that cost us a reconciliation

Our own payment writer used an ON CONFLICT clause that could not infer the partial unique index it was meant to target. Every insert into Postgres failed — and because the code also wrote to a file mirror, the site kept working perfectly. Receipts sent, orders completed, nothing in the logs a casual reader would notice. The database simply had no payments in it. If you take one thing from this article: assert that the row is there after you write it, and alert when it is not. A payment path that fails silently is worse than one that fails loudly.

What to store, and what not to

Store the gateway’s payment identifier, the event identifier, the amount, the currency, the status and your own order reference. That is enough to reconcile against the gateway’s own report, which is the only reconciliation that counts.

Do not store card numbers, and do not build anything that would make you want to. Under the Data Privacy Act (RA 10173) you are accountable for personal data you hold, and the cheapest compliance position is holding as little as possible. A gateway exists so that payment credentials never reach your server; keep it that way.

Testing it properly

  • Use the sandbox, then test one real peso. Sandboxes do not reproduce every real behaviour. One genuine low-value transaction end to end, including the receipt and the bookkeeping entry, is worth a week of sandbox testing.
  • Test the abandoned payment. Start a payment, close the tab, walk away. The order must not sit in a state that blocks the customer from trying again.
  • Test the duplicate. Replay the same webhook twice and confirm the second changes nothing — no second row, no second receipt.
  • Test the refund. Refunds arrive as their own events and are routinely forgotten until the first one is needed, in a hurry, in front of a customer.

When GCash is the wrong rail

Wallet limits make GCash a poor fit for large invoices, which is why our own checkout does not offer it. If you sell a ₱400,000 engagement, bank transfer against a reference code is simpler for both sides and costs neither of you a percentage. If you sell ₱500 items to consumers, GCash at the top of the checkout will out-convert a card form comfortably.

Cash on delivery deserves the same scrutiny. It remains a large share of Philippine e-commerce and it is not free — refused parcels carry the shipping both ways. Configure it against a refusal policy rather than leaving it on by default, which is the same argument we make about Shopify builds.

What this should cost to build

On an existing platform, adding a gateway is configuration and a day of testing. Wiring one properly into a custom checkout — webhooks, idempotency, receipts, refunds, reconciliation against the gateway’s report — is a piece of work, and it is the part that gets underestimated. Our API and systems integration work starts at ₱180,000, and a payment path is the kind of integration where the failure mode is money rather than inconvenience.

Which payment rails should our checkout offer?