What is the best way to verify a (Metamask) transaction has happened on NodeJS?

Use case:

A user buys a digital card with ether. After the user buys the digital card, a route on the server runs some code, makes a PDF, etc..

What is the recommended way to have a server (ExpressJS) verify that a tx has happened and is successful?

I was thinking of two options, but I'm pretty new to Ethereum so I'm probably missing some details here.

  1. Have the server listen to events from the contract. When an event comes in, it'll have the user's address and prove they paid. Than I can make the card.

The only thing that I'm concerned here with is that maybe some details of the card are private. And if it's in the logs, it'll be public. 🤔


  1. OR have the user sign the message locally, push it to the server and somehow send it to the contract. I'm not sure what the details are with this route.

Are there any recommended ways to accomplish something like this?

1 Answer 1


I followed the below approach for a use case in which transactions were generated from a front-end using React.js and Metamask and a back-end with Node.js:

  1. Transaction creation [React.js]: the user performs an action (e.g.: buy a digital card) in the website that triggers a call to a smart contract that needs to be confirmed with Metamask. The call should look something like this:

     // Transaction parameters
     const txParams = {
         from: fromAccount,
         to: toAccount,
         data: encodedData,
         value: (action === Action.SWAP_TRANSFER_ETH) ? value : 0,
         chainId: chainId,
     // Transaction execution in Ethereum from Metamask
     await window.ethereum.request({
         method: 'eth_sendTransaction',
         params: [txParams],
         .then(async (tx: string) => {
          // Actions from step 2
  2. Transaction submission to back-end [React.js]: once the transaction is created, you can send the transaction hash to the backend together with any further information you might need to store. In my case, I used axios to post data into Node.js:

             // Transaction details (extended)
             const params = {
                 from: fromAccount,
                 to: toAccount,
                 txHash: tx,
                 chainId: chainId,
                 status: 'pending',
                 lastUpdate: moment().unix()
                 //... other fields
             // Post tx data
             await axios.post(`${URL()}/ethereum/send`, params)
                 .then(res => {
                     // Show logs
  3. Transaction confirmation in back-end [Node.js]: here is where you need to check whether the transaction has been effectively confirmed, which means that you need to wait for a certain number of blocks validated after your transaction to avoid a potential chain reorganisation. In this answer I give background information on how to validate a transaction from Node.js:

ETH transactions confirmations counting

Finally, I was using a database to store data regarding the users' orders. In general, it is not recommended to store any sensitive data into the blockchain, and if you need to do so, it is better to hash the data and use it only as reference to ensure the integrity between what you stored in the database and the hash stored in the blockchain (e.g: in case you need traceability of your orders).

Regarding the number of confirmations required... not sure there is a global consensus, but have read around 10 to 12. It depends on how long you accept to wait and how important is to ensure the confirmations.

Hope this gives you an overall idea.

  • Thanks for the answer. Did you make any guards to cover tx hashes being visible on EtherScan? I.E. A user could grab another user's tx hash on EtherScan, send that to the server and try to replay the tx hash to get a free card?
    – GN.
    Dec 19, 2020 at 2:38
  • 1
    I saved every tx hash in the database, but just to keep traceability. Transactions do normally come with a nonce, so if anyone tries to reply a transaction with the same nonce, this will be discarded by nodes. As for the nonce, I was using this for every new tx: const nonce = await web3.eth.getTransactionCount(fromAddress, 'pending'); Dec 19, 2020 at 5:59
  • Ok, that makes sense. So in my case, the server might query the DB by TX hash, if it finds it — it knows this tx was already used. Otherwise it would know it was a new tx, and print a new card?
    – GN.
    Dec 19, 2020 at 19:00
  • My thinking is maybe a user could find your contract on Etherscan, copy a tx and post it to the server. Even though they could NOT replay the tx in Ethereum (because the nonce prevents that), they could send it to your server — and if the server (using Web3) is just to calling the chain to check “is this tx confirmed?” and it would return true — it would print a new card. (Even if the tx was used by another user, a previous day). But if the server checks the DB for the tx hash, it would prevent that. Is that basically the flow?
    – GN.
    Dec 19, 2020 at 19:00
  • 1
    Yep, good point. On server side, you need to ensure that any tx is not resent, so you better double check it. However, you would normally be using JWT or any similar token session so that only the front-end can send such requests to the server after user authentication. In any case, it is better to think on the worst case scenario, so keeping track of all tx will give you better control on your business flows. Dec 19, 2020 at 21:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.