⚠️ Secure payment verification

Once a payment has been sent and the XUMM platform payload resolves, there are some checks to perform.

So a payload completed the entire Payload life cycle. A payload was created, a user opened the payload, signed a transaction and XUMM sent the transaction to the XRP Ledger.

Now your application will receive (if configured, highly advisable) a WebHook callback, which should trigger your application to fetch the payload results.

You can also (highly advisable) fetch the payload results again on your "thank you" / return page, in case you didn't persist the payload results after receiving a webhook.

You now have your payload results, payload details with transaction information. There are a couple of remaining steps to make sure you REALLY did get paid. The steps below assume a regular Payment transaction in XRP was signed.

  1. Check if the Payload output contains meta.resolved. This value should be true, otherwise the payload is still pending (waiting for user interaction). Alternatively the Payload has been abandoned by the user.
  2. Check if the Payload output contains meta.signed. This value should be true, otherwise the user didn't sign the transaction.
  3. Check the response.dispatched_nodetype value. If you are expecting a real payment, make sure this value contains MAINNET. If you don't, you may be tricked into accepting a TESTNET payment.
  4. Check the response.txid value: this is the on ledger transaction hash. You have to verify this transaction on ledger. Please note that it may take ~4 seconds for a ledger to close, and slightly longer for the ledger and transaction info to propagate. You may want to repeat async/delay fetching this info if you don't get a result at first, or if your result contains a validated: false value. We have a helper lib (JS/TS) to fetch this data.
    XRPL Transaction Data fetcher npm version GitHub Actions NodeJS status CDNJS Browserified CDNJS Browserified Minified
    Alternatively you could use the JSON RPC (HTTP POST) method at eg. https://xrplcluster.com.
  5. After you fetched the transaction details, check the meta.delivered_amount value, to see if the amount of XRP (in drops, one million drops = one XRP) equals the expected amount to be paid.

Example (flow + code) using the xrpl-txdata package (JS/TS)

You sent a Sign Request (payload) to the XUMM platform, and you got a response informing you that the transaction was signed (per Webhook or WebSocket).

You now have the transaction hash of the transaction signed (Webhook: payloadResponse.txid, WebSocket: txid).

If you're lucky & check this transaction by the transaction hash on the XRP Ledger, it's already there, in a validated ledger. If however the transaction has been signed and XUMM sent the transaction to the XRP Ledger, but the transaction wasn't included in a closed ledger yet, you're at risk of fetching the transaction from the XRP Ledger, getting a "Not Found" error back, while if you would have checked a few seconds later, you'd have found the transaction.

To make this process easier, there's the xrpl-txdata NPM package for Javascript/TypeScript projects. This package takes care of:

  1. Connecting to the XRP Ledger, redundantly (multi node, failover) and reliably (auto timeout, auto retry)
  2. Fetching a transaction by hash
  3. Optionally: actively monitoring the XRP Ledger (all closed ledgers, all the transactions in those ledgers), waiting a set amount of time (seconds)

When your transaction has been found, the transaction outcome and effective, validated balance mutations will be returned to you.

const {TxData} = require('xrpl-txdata')

const TxHash = '8F3CE0481EF31A1BE44AD7D744D286B0F440780CD0056951948F93A803D47F8B'

const VerifyTx = new TxData([
  'wss://xrplcluster.com',
  'wss://xrpl.link',
  // Or:
  // 'wss://testnet.xrpl-labs.com'
], {
  OverallTimeoutMs: 6000,
  EndpointTimeoutMs: 1500,
  // If using testnet, see above:
  // AllowNoFullHistory: true
})

// Specify the transaction hash to verify, and the # of seconds
// to wait & monitor the XRPL if not found initially.
VerifyTx.getOne(TxHash, 20)
  .then(tx => {
    console.log(`Got the TX, details:`, tx)
  })