A Singapore Government Agency Website

Transaction Management and Monitoring

Last updated: 2026-01-26

Changelog

  • 2026-04-14
    • Added missing transaction.multiplepaymentsrefunded event
    • Added eventOccurredAt field to webhook payloads for all events.
    • Added “Out-of-Order Webhooks” section under “Troubleshooting Common Issues”
  • 2026-01-26
    • Added optional tags field to all webhook payloads
    • Updated iss value to https://paywith.wallet.gov.sg for all webhook payloads
  • 2025-12-23
    • Added errorDetails and sourceOfError fields to transaction.cancelled and transaction.pendingcancel webhook payloads
    • Added ErrorCode and SourceOfError reference tables

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:

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 partnerReturnUrl or partnerFailureReturnUrl might 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 eventOccurredAt field 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.completed
  • transaction.refunded
  • transaction.voided
  • transaction.cancelled
  • transaction.pendingcancel
  • transaction.multiplepayments
  • transaction.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

  1. Check domain whitelisting status
  2. Verify webhook URL accessibility
  3. Review webhook endpoint logs
  4. Use inquiry API as fallback

Duplicate Webhooks

  1. Implement idempotency using transaction ID
  2. Check for processing logic issues
  3. 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

  1. Use inquiry API to get latest status
  2. Check for clock synchronization issues
  3. Review transaction processing logs
  4. 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.

Chat with me!
Bot Launcher