Monitor Transactions

The transaction ID lets you monitor transaction status.

The transaction flow inherently operates asynchronously, involving the approval of the transaction, its subsequent signing, and the waiting period for the blockchain to mine the transaction.

To allow tracking of the transaction flow, Fordefi provides a unique transaction ID upon submission. This transaction ID serves as Fordefi's internal representation of the transaction and will enable you to monitor the status of the transaction. Use this ID to stay informed about the progress and current state of the transaction. Once the transaction has been created, it progresses according to the Transaction Lifecycle.

There are several approaches to monitoring transaction states:

  • Signing up for webhook notifications and monitoring the received webhooks.
  • Polling via the REST API

We strongly recommend using webhook notifications to monitor transactions, as they offer several advantages over the polling mechanism:

  • Minimal delays: With polling, you become aware of the transaction’s progress only in the next poll request, leading to potential staleness in your data up to the polling interval. In contrast, Webhooks provide real-time notifications, instantly informing you the moment a transaction makes progress.
  • No rate limit: The Fordefi REST API imposes rate limits, restricting the number of queries that can be sent by REST clients within a specific time interval. However, webhooks are not subject to rate limits since they are initiated by the Fordefi system itself, not by the client.
  • Efficient computation usage: Polling transactions involves repeatedly querying the transaction status, resulting in many unchanged responses. This can lead to redundant work for the client as no changes have occurred yet. Webhooks, on the other hand, notify you only when a transaction has made progress, minimizing unnecessary computation and optimizing efficiency.

Monitor transactions using webhooks

Webhook messages sent by Fordefi to your service use JSON structures, including the specification of the incoming POST requests format under the Callbacks section. See the Fordefi API reference for more information.

Webhooks send updates of transactions whenever their state is changed, including updates of incoming transactions (transactions whose origin is not managed by the organization).

Using the following HTTP Server, you can start monitoring transactions:

import http  
import http.server  
import json  
import logging

PORT = 8080

logger = logging.getLogger(__name__)

class WebhooksListener(http.server.BaseHTTPRequestHandler):  
    def do_POST(self):  
        logger.info("Received POST request")  
        content_length = int(self.headers["Content-Length"])  
        body = self.rfile.read(content_length)  
        event = json.loads(body)  
        event_type = event["event_type"]  
        logger.info(f"Received a new event of type: {event_type=}")  
        if event_type == "transaction_state_update":  
            self._on_transaction_update(event["event"])  
        self.send_response(http.HTTPStatus.OK, "OK")  
        self.end_headers()

    def _on_transaction_update(self, transaction_event) -> None:
        transaction_id = transaction_event["transaction_id"]
        state = transaction_event["state"]
        logger.info(f"Received a transaction update: {transaction_id=} changed state to {state=}")

    def log_message(format, *args):
        pass

def setup_logging() -> None:  
    handler = logging.StreamHandler()  
    handler.setFormatter(logging.Formatter("%(asctime)s:%(name)s:%(levelname)s:%(message)s"))  
    logger.setLevel(logging.INFO)  
    logger.addHandler(handler)

def main() -> None:  
    setup_logging()

    logger.info(f"Listening on port {PORT}...")
    http.server.HTTPServer(("0.0.0.0", PORT), WebhooksListener).serve_forever()


if __name__ == "__main__":  
    main()

Run the server:

$ python monitor.py

This output indicates that the server is up and running, listening to port 8080:

2023-07-18 12:22:35,417:__main__:INFO:Listening on port 8080...

Expose your server easily using ngrok. Copy the URL that is referenced in the Forwarding field (for example, https://84c5df439d74.ngrok-free.devin the sample output below) and configure the webhook using the instructions in Configure a Webhook.

$ ngrok http 8080  
    ngrok                                                                      
(Ctrl+C to quit)

Session Status                online
Account                       inconshreveable (Plan: Free)
Version                       3.0.0
Region                        United States (us)
Latency                       78ms
Web Interface                 http://127.0.0.1:4040
Forwarding                    https://84c5df439d74.ngrok-free.dev -> http://localhost:8080

Connections                   ttl     opn     rt1     rt5     p50     p90
                              0       0       0.00    0.00    0.00    0.00

Following the instructions in Validate a webhook to check your webhook setup.

You should see an indication of success in the web console:

And in your console, the server should output:

2023-07-18 12:29:37,721:__main__:INFO:Received POST request  
2023-07-18 12:29:37,721:__main__:INFO:Received a new event of type: event_type='transaction_state_update'  
2023-07-18 12:29:37,721:__main__:INFO:Received a transaction update: transaction_id='6b2a6f2b-2c2f-4c6b-9e1b-6d8f6a9b9c9d' changed state to state='pushed_to_blockchain'

Monitor transactions using REST API

If you prefer a synchronous API for monitoring the transactions, you can use the following example, which will poll until the transaction reaches the Completed state.

import argparse  
import logging  
import time  
import uuid

import requests

PORT = 8080

logger = logging.getLogger(__name__)

def setup_logging() -> None:  
    handler = logging.StreamHandler()  
    handler.setFormatter(logging.Formatter("%(asctime)s:%(name)s:%(levelname)s:%(message)s"))  
    logger.setLevel(logging.INFO)  
    logger.addHandler(handler)

def monitor_transaction(  
    access_token: str,  
    transaction_id: uuid.UUID,  
    polling_interval: int,  
) -> None:  
    setup_logging()

    logger.info(f"Monitoring transaction: {transaction_id=}")

    current_state = None

    while True:
        transaction = requests.get(
            url=f"https://api.fordefi.com/api/v1/transactions/{transaction_id}",
            headers={
                "Authorization": f"Bearer {access_token}",
                "Content-Type": "application/json",
            },
        ).json()
        new_state = transaction["state"]
        if new_state != current_state:
            logger.info(f"Transaction {transaction_id=} changed state from {current_state=} to {new_state=}")
            current_state = new_state
            if new_state == "completed":
                break
        else:
            logger.info(f"Transaction {transaction_id=} is still in state {current_state=}")
        logger.info(f"Sleeping for {polling_interval=} seconds")
        time.sleep(polling_interval)


def parse_args() -> argparse.Namespace:  
    parser = argparse.ArgumentParser()  
    parser.add_argument("--transaction-id", type=uuid.UUID, required=True, help="Transaction ID to monitor")  
    parser.add_argument("--access-token", type=str, required=True, help="Access token for Fordefi API")  
    parser.add_argument("--polling-interval", type=int, default=5, help="Polling interval in seconds")  
    return parser.parse_args()

if __name__ == "__main__":  
    args = parse_args()  
    monitor_transaction(  
        access_token=args.access_token,  
        transaction_id=args.transaction_id,  
        polling_interval=args.polling_interval,  
    )

Then monitor the transaction by running:

$ python monitor.py --transaction-id bd28ab46-5874-4470-8b98-b1531165e347 --access-token <YOUR-FORDEFI-REST-API-ACCESS-TOKEN>

Possible output:

2023-07-18 16:00:42,949:__main__:INFO:Monitoring transaction: transaction_id=UUID('bd28ab46-5874-4470-8b98-b1531165e347')  
2023-07-18 16:00:43,690:__main__:INFO:Transaction transaction_id=UUID('bd28ab46-5874-4470-8b98-b1531165e347') changed state from current_state=None to new_state='approved'  
2023-07-18 16:00:43,690:__main__:INFO:Sleeping for polling_interval=5 seconds  
2023-07-18 16:00:49,386:__main__:INFO:Transaction transaction_id=UUID('bd28ab46-5874-4470-8b98-b1531165e347') is still in state current_state='approved'  
2023-07-18 16:00:49,386:__main__:INFO:Sleeping for polling_interval=5 seconds  
2023-07-18 16:00:55,102:__main__:INFO:Transaction transaction_id=UUID('bd28ab46-5874-4470-8b98-b1531165e347') changed state from current_state='approved' to new_state='pushed_to_blockchain'  
2023-07-18 16:00:55,102:__main__:INFO:Sleeping for polling_interval=5 seconds  
2023-07-18 16:01:00,769:__main__:INFO:Transaction transaction_id=UUID('bd28ab46-5874-4470-8b98-b1531165e347') is still in state current_state='pushed_to_blockchain'  
2023-07-18 16:01:00,769:__main__:INFO:Sleeping for polling_interval=5 seconds  
2023-07-18 16:01:06,459:__main__:INFO:Transaction transaction_id=UUID('bd28ab46-5874-4470-8b98-b1531165e347') is still in state current_state='pushed_to_blockchain'  
2023-07-18 16:01:06,459:__main__:INFO:Sleeping for polling_interval=5 seconds  
2023-07-18 16:01:12,168:__main__:INFO:Transaction transaction_id=UUID('bd28ab46-5874-4470-8b98-b1531165e347') is still in state current_state='pushed_to_blockchain'  
2023-07-18 16:01:12,168:__main__:INFO:Sleeping for polling_interval=5 seconds  
2023-07-18 16:01:17,901:__main__:INFO:Transaction transaction_id=UUID('bd28ab46-5874-4470-8b98-b1531165e347') is still in state current_state='pushed_to_blockchain'  
2023-07-18 16:01:17,901:__main__:INFO:Sleeping for polling_interval=5 seconds  
2023-07-18 16:01:23,611:__main__:INFO:Transaction transaction_id=UUID('bd28ab46-5874-4470-8b98-b1531165e347') is still in state current_state='pushed_to_blockchain'  
2023-07-18 16:01:23,611:__main__:INFO:Sleeping for polling_interval=5 seconds  
2023-07-18 16:01:29,362:__main__:INFO:Transaction transaction_id=UUID('bd28ab46-5874-4470-8b98-b1531165e347') is still in state current_state='pushed_to_blockchain'  
2023-07-18 16:01:29,362:__main__:INFO:Sleeping for polling_interval=5 seconds  
2023-07-18 16:01:35,083:__main__:INFO:Transaction transaction_id=UUID('bd28ab46-5874-4470-8b98-b1531165e347') is still in state current_state='pushed_to_blockchain'  
2023-07-18 16:01:35,083:__main__:INFO:Sleeping for polling_interval=5 seconds  
2023-07-18 16:01:40,755:__main__:INFO:Transaction transaction_id=UUID('bd28ab46-5874-4470-8b98-b1531165e347') changed state from current_state='pushed_to_blockchain' to new_state='mined'  
2023-07-18 16:01:40,755:__main__:INFO:Sleeping for polling_interval=5 seconds  
2023-07-18 16:01:46,460:__main__:INFO:Transaction transaction_id=UUID('bd28ab46-5874-4470-8b98-b1531165e347') is still in state current_state='mined'  
2023-07-18 16:01:46,460:__main__:INFO:Sleeping for polling_interval=5 seconds  
2023-07-18 16:01:52,154:__main__:INFO:Transaction transaction_id=UUID('bd28ab46-5874-4470-8b98-b1531165e347') is still in state current_state='mined'  
2023-07-18 16:01:52,155:__main__:INFO:Sleeping for polling_interval=5 seconds  
2023-07-18 16:01:57,849:__main__:INFO:Transaction transaction_id=UUID('bd28ab46-5874-4470-8b98-b1531165e347') is still in state current_state='mined'  
2023-07-18 16:01:57,850:__main__:INFO:Sleeping for polling_interval=5 seconds  
2023-07-18 16:02:03,541:__main__:INFO:Transaction transaction_id=UUID('bd28ab46-5874-4470-8b98-b1531165e347') is still in state current_state='mined'  
2023-07-18 16:02:03,541:__main__:INFO:Sleeping for polling_interval=5 seconds  
2023-07-18 16:02:09,220:__main__:INFO:Transaction transaction_id=UUID('bd28ab46-5874-4470-8b98-b1531165e347') is still in state current_state='mined'  
2023-07-18 16:02:09,220:__main__:INFO:Sleeping for polling_interval=5 seconds  
2023-07-18 16:02:14,916:__main__:INFO:Transaction transaction_id=UUID('bd28ab46-5874-4470-8b98-b1531165e347') is still in state current_state='mined'  
2023-07-18 16:02:14,916:__main__:INFO:Sleeping for polling_interval=5 seconds  
2023-07-18 16:02:20,666:__main__:INFO:Transaction transaction_id=UUID('bd28ab46-5874-4470-8b98-b1531165e347') is still in state current_state='mined'  
2023-07-18 16:02:20,666:__main__:INFO:Sleeping for polling_interval=5 seconds  
2023-07-18 16:02:26,357:__main__:INFO:Transaction transaction_id=UUID('bd28ab46-5874-4470-8b98-b1531165e347') is still in state current_state='mined'  
2023-07-18 16:02:26,357:__main__:INFO:Sleeping for polling_interval=5 seconds  
2023-07-18 16:02:32,059:__main__:INFO:Transaction transaction_id=UUID('bd28ab46-5874-4470-8b98-b1531165e347') is still in state current_state='mined'  
2023-07-18 16:02:32,059:__main__:INFO:Sleeping for polling_interval=5 seconds  
2023-07-18 16:02:37,768:__main__:INFO:Transaction transaction_id=UUID('bd28ab46-5874-4470-8b98-b1531165e347') is still in state current_state='mined'  
2023-07-18 16:02:37,768:__main__:INFO:Sleeping for polling_interval=5 seconds  
2023-07-18 16:02:43,462:__main__:INFO:Transaction transaction_id=UUID('bd28ab46-5874-4470-8b98-b1531165e347') is still in state current_state='mined'  
2023-07-18 16:02:43,462:__main__:INFO:Sleeping for polling_interval=5 seconds  
2023-07-18 16:02:49,159:__main__:INFO:Transaction transaction_id=UUID('bd28ab46-5874-4470-8b98-b1531165e347') is still in state current_state='mined'  
2023-07-18 16:02:49,159:__main__:INFO:Sleeping for polling_interval=5 seconds  
2023-07-18 16:02:54,827:__main__:INFO:Transaction transaction_id=UUID('bd28ab46-5874-4470-8b98-b1531165e347') is still in state current_state='mined'  
2023-07-18 16:02:54,827:__main__:INFO:Sleeping for polling_interval=5 seconds  
2023-07-18 16:03:00,738:__main__:INFO:Transaction transaction_id=UUID('bd28ab46-5874-4470-8b98-b1531165e347') changed state from current_state='mined' to new_state='completed'