GrabPay
Accept payments from GrabPay, Southeast Asia's leading digital wallet available in Singapore and Malaysia through Omise.
Payment Flowโ

The image illustrates the complete redirect checkout process including phone number entry, OTP verification, payment method selection, and final confirmation.
Overviewโ
GrabPay is the payment wallet integrated into the Grab super-app, which provides ride-hailing, food delivery, and financial services across Southeast Asia. Customers can pay using their GrabPay balance with instant confirmation.
Key Features:
- โ Massive reach - 187 million users across 8 countries
- โ Fast confirmation - Near real-time payment verification (typically within seconds)
- โ Mobile-first - Optimized for smartphone users
- โ Trusted brand - Part of Grab ecosystem
- โ No checkout friction - One-tap payment in Grab app
- โ Regional coverage - Multi-country support
Supported Regionsโ
| Region | Currency | Min Amount | Max Amount | API Version |
|---|---|---|---|---|
| Singapore | SGD | $1.00 | $5,000.00 | 2017-11-02 |
| Malaysia | MYR | RM1.00 | RM1,500.00 | 2017-11-02 |
Transaction Limits by Countryโ
Singapore (SGD)โ
| Verification Level | Per Transaction | Daily Limit | Monthly Limit |
|---|---|---|---|
| Basic (Phone only) | $500 | $2,000 | $5,000 |
| Plus (ID verified) | $500 | $5,000 | $30,000 |
Malaysia (MYR)โ
| Verification Level | Per Transaction | Daily Limit | Monthly Limit |
|---|---|---|---|
| Basic (Phone only) | RM1,500 | RM1,500 | RM3,000 |
| Plus (ID verified) | RM1,500 | RM5,000 | RM10,000 |
How It Worksโ
Customer Experience:
- Customer selects GrabPay at checkout
- Redirected to GrabPay authorization page
- Opens Grab app (deep link)
- Reviews payment details
- Confirms with PIN/biometric
- Returns to merchant site
Typical completion time: 1-2 minutes
Implementationโ
Step 1: Create GrabPay Sourceโ
- cURL
- Node.js
- PHP
- Python
- Ruby
- Go
- Java
- C#
curl https://api.omise.co/sources \
-u skey_test_YOUR_SECRET_KEY: \
-d "type=grabpay" \
-d "amount=10000" \
-d "currency=SGD"
const omise = require('omise')({
secretKey: 'skey_test_YOUR_SECRET_KEY'
});
const source = await omise.sources.create({
type: 'grabpay',
amount: 10000, // SGD 100.00
currency: 'SGD'
});
<?php
$source = OmiseSource::create(array(
'type' => 'grabpay',
'amount' => 10000,
'currency' => 'SGD'
));
?>
import omise
omise.api_secret = 'skey_test_YOUR_SECRET_KEY'
source = omise.Source.create(
type='grabpay',
amount=10000,
currency='SGD'
)
require 'omise'
Omise.api_key = 'skey_test_YOUR_SECRET_KEY'
source = Omise::Source.create({
type: 'grabpay',
amount: 10000,
currency: 'SGD'
})
source, err := client.Sources().Create(&operations.CreateSource{
Type: "grabpay",
Amount: 10000,
Currency: "SGD",
})
Source source = client.sources().create(new Source.CreateParams()
.type("grabpay")
.amount(10000L)
.currency("SGD"));
var source = await client.Sources.Create(new CreateSourceRequest
{
Type = "grabpay",
Amount = 10000,
Currency = "SGD"
});
Response:
{
"object": "source",
"id": "src_test_5rt6s9vah5lkvi1rh9c",
"type": "grabpay",
"flow": "redirect",
"amount": 10000,
"currency": "SGD"
}
Step 2: Create Chargeโ
curl https://api.omise.co/charges \
-u skey_test_YOUR_SECRET_KEY: \
-d "amount=10000" \
-d "currency=SGD" \
-d "source=src_test_5rt6s9vah5lkvi1rh9c" \
-d "return_uri=https://yourdomain.com/payment/callback"
Step 3: Redirect Customerโ
app.post('/checkout/grabpay', async (req, res) => {
try {
const { amount, currency, order_id } = req.body;
// Validate currency
const supportedCurrencies = ['SGD', 'MYR'];
if (!supportedCurrencies.includes(currency)) {
return res.status(400).json({
error: 'GrabPay supports SGD and MYR only'
});
}
// Validate amount by currency
const limits = {
SGD: { min: 100, max: 500000 },
MYR: { min: 100, max: 150000 }
};
if (amount < limits[currency].min || amount > limits[currency].max) {
return res.status(400).json({
error: `Amount out of range for ${currency}`
});
}
// Create source
const source = await omise.sources.create({
type: 'grabpay',
amount: amount,
currency: currency
});
// Create charge
const charge = await omise.charges.create({
amount: amount,
currency: currency,
source: source.id,
return_uri: `${process.env.BASE_URL}/payment/callback`,
metadata: {
order_id: order_id
}
});
// Redirect to GrabPay
res.redirect(charge.authorize_uri);
} catch (error) {
console.error('GrabPay error:', error);
res.status(500).json({ error: error.message });
}
});
Step 4: Handle Returnโ
app.get('/payment/callback', async (req, res) => {
try {
const chargeId = req.query.charge_id;
const charge = await omise.charges.retrieve(chargeId);
if (charge.status === 'successful') {
// Payment successful
await processOrder(charge.metadata.order_id);
res.redirect('/payment-success');
} else if (charge.status === 'failed') {
// Payment failed
res.redirect('/payment-failed?reason=' + charge.failure_message);
} else {
// Still pending
res.redirect('/payment-pending');
}
} catch (error) {
res.redirect('/payment-error');
}
});
Step 5: Handle Webhookโ
app.post('/webhooks/omise', (req, res) => {
const event = req.body;
if (event.key === 'charge.complete' && event.data.source.type === 'grabpay') {
const charge = event.data;
if (charge.status === 'successful') {
processOrder(charge.metadata.order_id);
} else if (charge.status === 'failed') {
handleFailedPayment(charge.metadata.order_id);
}
}
res.sendStatus(200);
});
Complete Implementation Exampleโ
// Express.js server
const express = require('express');
const omise = require('omise')({
secretKey: process.env.OMISE_SECRET_KEY
});
const app = express();
app.use(express.json());
// Amount limits by currency
const LIMITS = {
SGD: { min: 100, max: 500000 },
MYR: { min: 100, max: 150000 }
};
// Checkout page
app.post('/checkout/grabpay', async (req, res) => {
try {
const { amount, currency, order_id } = req.body;
// Validate currency
if (!['SGD', 'MYR'].includes(currency)) {
return res.status(400).json({
error: 'GrabPay only supports SGD and MYR'
});
}
// Validate amount
const { min, max } = LIMITS[currency];
if (amount < min || amount > max) {
return res.status(400).json({
error: `Amount must be between ${min} and ${max} ${currency}`
});
}
// Create source
const source = await omise.sources.create({
type: 'grabpay',
amount: amount,
currency: currency
});
// Create charge
const charge = await omise.charges.create({
amount: amount,
currency: currency,
source: source.id,
return_uri: `${process.env.BASE_URL}/payment/callback`,
metadata: {
order_id: order_id,
payment_method: 'grabpay'
}
});
// Return authorization URL
res.json({
authorize_uri: charge.authorize_uri,
charge_id: charge.id
});
} catch (error) {
console.error('GrabPay error:', error);
res.status(500).json({ error: error.message });
}
});
// Callback handler
app.get('/payment/callback', async (req, res) => {
try {
const chargeId = req.query.charge_id;
const charge = await omise.charges.retrieve(chargeId);
if (charge.status === 'successful') {
res.redirect(`/order-success?order=${charge.metadata.order_id}`);
} else {
res.redirect(`/payment-failed?charge=${chargeId}`);
}
} catch (error) {
res.redirect('/payment-error');
}
});
// Webhook handler
app.post('/webhooks/omise', (req, res) => {
const event = req.body;
if (event.key === 'charge.complete') {
const charge = event.data;
if (charge.source.type === 'grabpay') {
if (charge.status === 'successful') {
updateOrderStatus(charge.metadata.order_id, 'paid');
sendConfirmation(charge.metadata.customer_email);
} else {
updateOrderStatus(charge.metadata.order_id, 'failed');
}
}
}
res.sendStatus(200);
});
app.listen(3000);
Void and Refund Supportโ
Voiding Chargesโ
GrabPay supports voiding within 24 hours of charge creation:
// Void immediately (full amount only)
const refund = await omise.charges.refund('chrg_test_...', {
amount: 10000 // Full amount
});
if (refund.voided) {
console.log('Charge was voided (within 24 hours)');
}
Refundsโ
Full and partial refunds supported within 30 days:
// Full refund
const fullRefund = await omise.charges.refund('chrg_test_...', {
amount: 10000
});
// Partial refund
const partialRefund = await omise.charges.refund('chrg_test_...', {
amount: 5000 // Half refund
});
GrabPay supports both full and partial refunds within 30 days of the original transaction.
Common Issues & Troubleshootingโ
Issue: Customer doesn't have Grab appโ
Cause: Customer selected GrabPay but doesn't have Grab app installed
Solution:
- Display clear instructions before payment
- Check platform (iOS/Android)
- Provide app download links
function checkGrabApp() {
const isIOS = /iPhone|iPad|iPod/i.test(navigator.userAgent);
const isAndroid = /Android/i.test(navigator.userAgent);
if (!isIOS && !isAndroid) {
alert('GrabPay requires the Grab mobile app. Please use a mobile device.');
return false;
}
return true;
}
Issue: Insufficient balanceโ
Error: Payment declined
Solution:
- Show clear error message
- Allow customer to top up GrabPay wallet
- Offer alternative payment methods
if (charge.failure_code === 'insufficient_balance') {
showMessage('Insufficient GrabPay balance. Please top up or use another payment method.');
}
Issue: Currency mismatchโ
Error: Unsupported currency
Solution:
function validateGrabPayCurrency(currency, country) {
const currencyMap = {
'SG': 'SGD',
'MY': 'MYR'
};
const expectedCurrency = currencyMap[country];
if (currency !== expectedCurrency) {
throw new Error(`Use ${expectedCurrency} for ${country}`);
}
}
Issue: Payment timeoutโ
Cause: Customer didn't complete payment within time limit
Solution:
- Set 15-minute timeout
- Allow retry with new charge
- Send reminder
const TIMEOUT = 15 * 60 * 1000; // 15 minutes
setTimeout(() => {
if (!paymentConfirmed) {
showTimeoutMessage();
allowRetry();
}
}, TIMEOUT);
Best Practicesโ
1. Display Clear Instructionsโ
<div class="grabpay-instructions">
<h3>Pay with GrabPay</h3>
<ol>
<li>Make sure you have the Grab app installed</li>
<li>Ensure sufficient balance in your GrabPay wallet</li>
<li>You'll be redirected to the Grab app</li>
<li>Authenticate and confirm the payment</li>
</ol>
<p>Don't have enough balance? <a href="https://grab.com/sg/pay/">Top up now</a></p>
</div>
2. Handle Mobile Deep Linksโ
function openGrabApp(authorizeUri) {
if (/iPhone|iPad|iPod|Android/i.test(navigator.userAgent)) {
window.location = authorizeUri;
// Check if app opened
setTimeout(() => {
if (!document.hidden) {
showInstallAppMessage();
}
}, 2000);
} else {
alert('GrabPay is only available on mobile devices');
}
}
3. Validate Amount by Currencyโ
function validateAmount(amount, currency) {
const limits = {
SGD: { min: 100, max: 500000, label: '$' },
MYR: { min: 100, max: 150000, label: 'RM' }
};
const { min, max, label } = limits[currency];
if (amount < min) {
return `Minimum amount is ${label}${min / 100}`;
}
if (amount > max) {
return `Maximum amount is ${label}${max / 100}`;
}
return null; // Valid
}
4. Use Webhooksโ
// Webhook is primary notification
app.post('/webhooks/omise', handleWebhook);
// Callback is backup
app.get('/payment/callback', handleCallback);
5. Handle Regional Differencesโ
const GRABPAY_CONFIG = {
SG: {
currency: 'SGD',
minAmount: 100,
maxAmount: 500000,
displayName: 'GrabPay (Singapore)'
},
MY: {
currency: 'MYR',
minAmount: 100,
maxAmount: 150000,
displayName: 'GrabPay (Malaysia)'
}
};
FAQโ
What countries support GrabPay?
GrabPay is available in Singapore and Malaysia through Omise.
Do customers need a Grab account?
Yes, customers must have the Grab app installed and a GrabPay wallet activated. The app is free and available on iOS and Android.
What are the transaction limits?
Limits vary by country:
- Singapore: $1.00 - $5,000.00 per transaction
- Malaysia: RM1.00 - RM1,500.00 per transaction
Daily and monthly limits depend on customer verification level.
How long does settlement take?
GrabPay settlements typically occur within 1-3 business days. Check your Omise dashboard for specific settlement schedules for your account.
Can I refund GrabPay payments?
Yes, GrabPay supports both full and partial refunds within 30 days of the original transaction. Voiding is available within 24 hours.
What if customer has insufficient balance?
The payment will be declined. Customers can top up their GrabPay wallet via:
- Credit/debit card
- Bank transfer
- PayLater options
Provide clear error messages and offer alternative payment methods.
Does GrabPay work on desktop?
GrabPay requires the Grab mobile app, so it's mobile-only. Desktop users should be shown alternative payment methods.
Testingโ
Test Modeโ
GrabPay can be tested in test mode using your test API keys. In test mode:
Test Credentials:
- Use test API keys (skey_test_xxx)
- Test all supported countries: Singapore, Malaysia
- Test different currencies: SGD, MYR
Test Flow:
- Create source and charge with test API keys
- Redirect customer to test
authorize_uri - Test authorization page will be displayed
- Use dashboard Actions to simulate payment success/failure
- Verify webhook delivery and return_uri handling
Testing Implementation:
// Test GrabPay for different countries
const testCountries = ['SG', 'MY'];
for (const country of testCountries) {
const config = countryConfig[country];
const source = await omise.sources.create({
type: 'grabpay',
amount: config.minAmount,
currency: config.currency
});
const charge = await omise.charges.create({
amount: config.minAmount,
currency: config.currency,
source: source.id,
return_uri: 'https://example.com/callback'
});
console.log(`Test ${country}:`, charge.authorize_uri);
}
Test Scenarios:
- Successful payment: Verify order completion workflow
- Failed payment: Test error handling and retry mechanisms
- Multi-country: Test each supported country separately
- Amount limits: Verify country-specific limits are enforced
- Currency validation: Ensure proper currency for each country
- Mobile flow: Test redirect and deep-linking on mobile
- Timeout: Test abandoned payment handling
Important Notes:
- Test mode won't connect to real Grab servers
- Use Omise Dashboard to mark test charges as successful/failed
- Test each country/currency combination before going live
- Verify webhooks are received for all status changes
- Test on both iOS and Android if supporting mobile
For comprehensive testing guidelines, see the Testing Documentation.
Related Resourcesโ
- Digital Wallets Overview - All wallet options
- TrueMoney - Thailand wallet
- ShopeePay - Alternative wallet
- Touch 'n Go - Malaysia wallet
- Refunds - Refund policies
- Testing - Test GrabPay integration