Transaction Management and Monitoring
Last updated: 2026-01-26
Changelog
- 2026-04-14
- Added missing
transaction.multiplepaymentsrefundedevent - Added
eventOccurredAtfield to webhook payloads for all events. - Added “Out-of-Order Webhooks” section under “Troubleshooting Common Issues”
- Added missing
- 2026-01-26
- Added optional
tagsfield to all webhook payloads - Updated
issvalue tohttps://paywith.wallet.gov.sgfor all webhook payloads
- Added optional
- 2025-12-23
- Added
errorDetailsandsourceOfErrorfields totransaction.cancelledandtransaction.pendingcancelwebhook payloads - Added ErrorCode and SourceOfError reference tables
- Added
This guide covers best practices for transaction investigation, monitoring, and implementing robust payment workflows. It addresses scenarios where transactions require extra handling beyond the standard checkout flow.
Overview
When users don’t follow the happy path during checkout, transactions may require additional investigation and handling. This document outlines the approaches partners can use to ensure robust transaction processing and monitoring.
Transaction Investigation Methods
Partners have two primary approaches for investigating and monitoring transactions:
1. Inquiry Approach (Pull Method)
Use the GovWallet APIs to actively query transaction status using either:
- Your partner transaction ID (
cart.transactionId) - GovWallet transaction ID (
govwalletTransactionId)
Available Inquiry APIs:
/api/transactions/getByCartTransactionId- Query using your transaction ID/api/transaction/getByGovwalletTransactionId- Query using GovWallet’s transaction ID/api/customer/transactions/get- Get all transactions for a customer within a date range
When to use:
- For real-time transaction status checks
- When implementing retry mechanisms
- For customer service inquiries
- For reconciliation processes
2. Notification Approach (Push Method)
Configure GovWallet to send transaction status updates to your backend systems as webhook.
Note:
- The hostname of your webhook URLs must be whitelisted by GovWallet.
- Your
partnerReturnUrlorpartnerFailureReturnUrlmight be called before or after our webhooks, or not called at all due to user drop off and networking conditions. - Webhook notifications might arrive out-of-order due to network delays or temporary downtime. Use the
eventOccurredAtfield in each payload to determine the intended sequence of events — it carries the timestamp of when the event actually occurred, independent of delivery time. See Out-of-Order Webhooks for an example of how to apply this.
Current Solution
Webhook system with multiple event types and structured payloads.
Event-driven notifications:
transaction.completedtransaction.refundedtransaction.voidedtransaction.cancelledtransaction.pendingcanceltransaction.multiplepaymentstransaction.multiplepaymentsrefunded
Payload format:
transaction.completed
{
"data": {
"campaign": "campaign_1",
"cart": {
// ... cart details
},
"tags": ["tag1", "tag2"], // optional, array of tags associated with the transaction
"result": {
"successfulTransaction": {
"govwalletTransactionId": "a8a97e11-61f1-4240-a432-0a3e25791d43",
"payment": {
"credits": {
"amountCents": 1000,
"name": "Sample Campaign Credit"
},
"outOfPocket": {
"amountCents": 2100,
"method": "Card",
"paymentServiceProvider": "2c2p"
},
"totalAmountCents": 3100
},
"uniqueUserId": "defaultHashedNric"
}
}
},
"event": "transaction.completed",
"eventOccurredAt": "2026-04-14T11:12:06.88471+08:00",
"exp": 1756893560,
"iat": 1756892660,
"iss": "https://paywith.wallet.gov.sg",
"nbf": 1756892660
}transaction.refunded
{
"data": {
"campaign": "campaign_1",
"cart": {
// ... cart details
},
"tags": ["tag1", "tag2"], // optional, array of tags associated with the transaction
"result": {
"transaction": {
"govwalletTransactionId": "a8a97e11-61f1-4240-a432-0a3e25791d43",
"payment": {
"credits": {
"amountCents": 1000,
"name": "Sample Campaign Credit"
},
"outOfPocket": {
"amountCents": 2100,
"method": "PayNow"
},
"totalAmountCents": 3100
},
"uniquePartnerReference": "123",
"uniqueUserId": "defaultHashedNric"
}
}
},
"event": "transaction.refunded",
"eventOccurredAt": "2026-04-14T11:12:06.88471+08:00",
"exp": 1756893560,
"iat": 1756892660,
"iss": "https://paywith.wallet.gov.sg",
"nbf": 1756892660
}transaction.voided
{
"data": {
"campaign": "campaign_1",
"cart": {
// ... cart details
},
"tags": ["tag1", "tag2"], // optional, array of tags associated with the transaction
// ... result (same as transaction.refunded)
},
"event": "transaction.voided",
"eventOccurredAt": "2026-04-14T11:12:06.88471+08:00",
"exp": 1756893560,
"iat": 1756892660,
"iss": "https://paywith.wallet.gov.sg",
"nbf": 1756892660
}transaction.cancelled
{
"data": {
"campaign": "campaign_1",
"cart": {
// ... cart details
},
"tags": ["tag1", "tag2"], // optional, array of tags associated with the transaction
"result": {
"error": {
"errorCode": "TRANSACTION_CANCELLED",
"errorMessage": "...", // varies by reason (refer to table below)
"errorDetails": "...", // optional, additional details from external providers
"sourceOfError": "..." // indicates source of error (see table below)
}
}
},
"event": "transaction.cancelled",
"eventOccurredAt": "2026-04-14T11:12:06.88471+08:00",
"exp": 1756893560,
"iat": 1756892660,
"iss": "https://paywith.wallet.gov.sg",
"nbf": 1756892660
}Error Object Fields:
| Field | Type | Description |
|---|---|---|
errorCode |
string | Error code indicating why the transaction failed (see ErrorCode table) |
errorMessage |
string | Human-readable error message |
errorDetails |
string (optional) | Additional error details from external providers. Omitted for internal errors to avoid exposing sensitive information. |
sourceOfError |
string | Indicates the source of the error (see SourceOfError table) |
ErrorCode values:
| ErrorCode | Description | Applicable Flow |
|---|---|---|
PAYMENT_FAILED |
Payment processing failed at the payment provider | Checkout Page, Checkout QR |
TRANSACTION_CANCELLED |
Transaction was cancelled by system or cleanup worker | Checkout Page, Checkout QR |
TRANSACTION_CANCELLED_BY_USER |
Transaction was cancelled manually by the user | Checkout Page, Checkout QR |
TRANSACTION_CANCELLED_BY_POS |
Transaction was cancelled manually by the POS | Checkout QR |
QR_RESCAN |
QR code rescan triggered by the POS | Checkout QR |
QR_EXPIRED |
QR code has expired | Checkout QR |
CART_EXPIRED |
Cart has expired | Checkout Page, Checkout QR |
INSUFFICIENT_CREDITS |
Customer has insufficient GovWallet credits balance | Checkout Page, Checkout QR |
SourceOfError values:
| SourceOfError | Description | errorDetails exposed? |
|---|---|---|
2c2p |
Error from 2C2P payment provider | Yes |
ocbc |
Error from OCBC payment provider | Yes |
walletCore |
Error from WalletCore API | Yes |
internal |
Internal technical errors | No (omitted) |
Scenarios & Respective errorMessage:
| Scenario | errorMessage |
|---|---|
| Customer cancellation at checkout | "customer cancelled transaction" |
| Transaction cancelled during automated system cleanup¹ | "transaction was cancelled" |
| Transaction cancelled via API call | "transaction earmark cancelled manually" |
transaction.pendingcancel
{
"data": {
"campaign": "campaign_1",
"cart": {
// ... cart details
},
"tags": ["tag1", "tag2"], // optional, array of tags associated with the transaction
"result": {
"error": {
"errorCode": "TRANSACTION_PENDING_CANCEL",
"errorMessage": "...", // varies by reason (refer to table below)
"errorDetails": "...", // optional, additional details from external providers
"sourceOfError": "..." // indicates source of error (see SourceOfError table above)
}
}
},
"event": "transaction.pendingcancel",
"eventOccurredAt": "2026-04-14T11:12:06.88471+08:00",
"exp": 1756893560,
"iat": 1756892660,
"iss": "https://paywith.wallet.gov.sg",
"nbf": 1756892660
}Scenarios & Respective errorMessage:
| Scenario | errorMessage |
|---|---|
| Transaction set to pending cancel during automated system cleanup¹ | "transaction has expired and is pending cancellation" |
| Transaction set to pending cancel when payment fails | "payment failed and transaction is pending cancellation" |
transaction.multiplepayments
{
"data": {
"campaign": "campaign_1",
"cart": {
// ... cart details
},
"tags": ["tag1", "tag2"], // optional, array of tags associated with the transaction
"result": {
"transaction": {
"govwalletTransactionId": "a8a97e11-61f1-4240-a432-0a3e25791d43",
"payment": {
"credits": {
"amountCents": 1000,
"name": "Sample Campaign Credit"
},
"outOfPocket": {
"amountCents": 2100,
"method": "PayNow"
},
"totalAmountCents": 3100
},
"uniquePartnerReference": "123",
"uniqueUserId": "defaultHashedNric"
}
}
},
"event": "transaction.multiplepayments",
"eventOccurredAt": "2026-04-14T11:12:06.88471+08:00",
"exp": 1756893560,
"iat": 1756892660,
"iss": "https://paywith.wallet.gov.sg",
"nbf": 1756892660
}transaction.multiplepaymentsrefunded
{
"data": {
"campaign": "campaign_1",
"cart": {
// ... cart details
},
"expiresInMinutes": 15,
"nonce": "27c020272ee4-3959-87a4-5c22-9a378bbe",
"result": {
"transaction": {
"govwalletTransactionId": "c9b568cd-ce08-40e6-9c25-bc5db632458d",
"payment": {
"credits": {
"amountCents": 1000,
"name": "SG Culture Pass credits"
},
"outOfPocket": {
"amountCents": 2100,
"method": "PayNow"
},
"totalAmountCents": 3100
},
"uniquePartnerReference": "fake-duplicate-ref-001",
"uniqueUserId": "defaultHashedNric"
}
}
},
"event": "transaction.multiplepaymentsrefunded",
"eventOccurredAt": "2026-04-14T11:12:06.88471+08:00",
"exp": 1776137406,
"iat": 1776136506,
"iss": "non-prd.dist.wallet.gov.sg",
"nbf": 1776136506
}Key features:
- Structured event types in payload
- Same transaction data as normal redirect flow
- Better for event-driven architectures
Configuration needed:
- Webhook URL(s) for each environment and issuer (up to 10)
Deprecated Solution
Upon a successful transaction, the same payload sent from the user’s browser to partnerReturnUrl can also be sent directly from the GovWallet backend to the same partnerReturnUrl, or to other webhook URLs that you specify.
Note: This solution has been deprecated in favour of the current webhook implementation, which supports delivering different payloads for different transaction events. Contact the GovWallet team to plan your migration to the current solution.
Implementation Recommendations
Choosing Your Approach
For tight launch timelines:
- Implement 2a (Current Solution)
For robust long-term architecture:
- Implement combination of 1 (Inquiry) + 2a (Current)
- Use inquiry for real-time checks
- Use webhooks (2a) for automated processing
Setup Requirements
For Current Solution
Contact the GovWallet team with:
- Webhook URL for each environment
For Inquiry Approach (1)
- Contact the GovWallet team to ensure you have appropriate OAuth scopes
- Set up monitoring and alerting for failed queries
Troubleshooting Common Issues
Webhook Not Received
- Check domain whitelisting status
- Verify webhook URL accessibility
- Review webhook endpoint logs
- Use inquiry API as fallback
Duplicate Webhooks
- Implement idempotency using transaction ID
- Check for processing logic issues
- Ensure database constraints prevent duplicates
Out-of-Order Webhooks
Webhooks may be delivered in a different order than the events actually occurred. Use the eventOccurredAt field to reconstruct the correct sequence.
Example:
| Event | eventOccurredAt |
|
|---|---|---|
| Actual order | transaction.completed |
2026-04-14T10:00:00.000+08:00 |
transaction.refunded |
2026-04-14T10:05:00.000+08:00 |
|
| Delivery order | transaction.refunded |
2026-04-14T10:05:00.000+08:00 |
transaction.completed |
2026-04-14T10:00:00.000+08:00 |
Even though transaction.refunded arrived first, its eventOccurredAt is later than that of transaction.completed. Sorting by eventOccurredAt gives you the correct sequence: the transaction was completed before it was refunded.
Transaction Status Mismatches
- Use inquiry API to get latest status
- Check for clock synchronization issues
- Review transaction processing logs
- Contact GovWallet support for investigation
For implementation assistance or to configure notification methods, contact the GovWallet team through your designated support channels.
¹ An automated system process that periodically cleans up or updates incomplete transactions / payment failures.