Real-time Updates
Enhance your Cardano escrow application with real-time transaction updates using Blockfrost webhooks and ngrok for local development.
Introduction
In this section, we'll enhance our transaction list by implementing real-time updates using Blockfrost webhooks. This will provide a much better user experience by showing immediate updates when transactions are confirmed on the blockchain. However note that in order to implement this in a production application, you will need to implement proper rollback protection and handle lost transactions.
Webhook Overview
Webhooks provide a mechanism for receiving real-time notifications about blockchain events. Instead of constantly polling for transaction confirmation updates, our application will receive instant notifications when transactions are confirmed (Based on the confirmation threshold set in the webhook configuration) on the Cardano blockchain.
Our hybrid approach uses:
Initial polling: For pending transactions when they're first created
Webhooks: For confirmation notifications when the blockchain confirms transactions
Key advantages of this hybrid approach:
Real-time Updates: Instant notifications when transactions are confirmed, without waiting for the next poll cycle
Reduced API Usage: Significantly fewer polling requests compared to a pure polling solution
Better User Experience: More immediate UI updates for confirmed transactions
Server Resource Efficiency: Lower overall load on both client and server
Implementation Steps
Install ngrok (a tunneling service) and start your development server at
http://localhost:3000
Set up Blockfrost webhook and have them send notifications to your ngrok URL with the endpoint
/api/webhooks/blockfrost
Implement Next.js route handler for webhook endpoint
src/app/api/webhooks/blockfrost/route.ts
Handle webhook events in the endpoint
1. Install and Configure ngrok
Since Blockfrost webhooks require a public URL to send notifications to, first we'll use ngrok to expose your local development server.
Installing ngrok
Sign up for a free ngrok account at dashboard.ngrok.com
Install ngrok using npm:
npm install -g ngrok
Configure ngrok with your auth token (found in your ngrok dashboard):

ngrok config add-authtoken YOUR_AUTH_TOKEN
Start your Next.js development server if it's not already running:
npm run dev
In a separate terminal, start ngrok to expose your local server:
ngrok http 3000
After Starting ngrok
Once ngrok is running, you'll see a terminal display with connection information
Look for the 'Forwarding' URL that looks like
https://xxxx-xxx-xxx-xxx-xxx.ngrok-free.app
Copy this URL - you'll need it for configuring your Blockfrost webhook
Keep this terminal window open as long as you need the tunnel active
2. Set Up Blockfrost Webhook
Create a Blockfrost account at blockfrost.io
Navigate to the Webhooks section in your Blockfrost dashboard
Click the 'Create webhook' button to create a new webhook
Configure your webhook with these settings:

Webhook name: Give it a descriptive name (e.g., 'my-app-escrow-lock-trigger-ngrok')
Endpoint URL: Enter your ngrok URL followed by your webhook endpoint path (e.g., 'https://xxxx-xxx-xxx-xxx-xxx.ngrok-free.app/api/webhook/blockfrost')
Network: Select 'Cardano preprod' for testnet development
Status: Ensure it's enabled
Trigger: Select 'Transaction'
Required confirmations: Set to your desired confirmation threshold (typically 1-3 for testing)
Trigger conditions:
Set condition type to 'recipient'
Set operator to 'equal to'
Enter your escrow script address (this can be found in the script deployment data or derived from the validator hash using the Anvil API)
Click 'Save webhook' to finalize the configuration
Important: Save the 'Auth token' provided by Blockfrost, and update your local
.env.local
file with yourBLOCKFROST_WEBHOOK_SECRET
.
3. Create the Webhook Endpoint
Let's create the webhook endpoint src/app/api/webhooks/blockfrost/route.ts
that will receive notifications from Blockfrost:
// src/app/api/webhooks/blockfrost/route.ts
import { NextResponse } from "next/server";
import { upsertWallet, upsertTx } from "@/lib/db";
import { TX_STATUS } from "@/lib/types";
export async function POST(request: Request) {
try {
const body = await request.json();
for (const event of body.payload) {
const wallet = event.inputs?.[0]?.address;
const txHash = event.tx.hash;
const amount = event.outputs?.[0]?.amount.find(
(a: { unit: string; quantity: string }) => a.unit === "lovelace"
)?.quantity;
if (wallet && amount) {
upsertWallet(wallet);
upsertTx(txHash, wallet, Number(amount), TX_STATUS.CONFIRMED);
}
}
} catch (err) {
console.error("Failed to handle webhook:", err);
}
return NextResponse.json({ ok: true });
}
5. Update Your BlockfrostWebhook URL
Now that your webhook endpoint is set up and ngrok is running:
Return to the Blockfrost dashboard
Update your webhook with the new ngrok URL:
https://[your-ngrok-url]/api/webhooks/blockfrost
Make sure you press 'Save Webhook' to save the changes
6. Webhook Payload Structure
Blockfrost will send notifications with a payload structure like this:
{
"id": "webhook-id",
"webhook_id": "your-webhook-id",
"created": 1625558414,
"api_version": 0,
"type": "transaction",
"payload": [
{
"tx": {
"hash": "transaction_hash_here"
},
"inputs": [
{
"address": "wallet_address_here"
}
],
"outputs": [
{
"amount": [
{
"unit": "lovelace",
"quantity": "amount_in_lovelace"
}
]
}
]
}
]
}
7. Smart Polling Implementation
Let's implement a smart polling mechanism in src/hooks/useTransactions.ts
that only activates when there are pending transactions. This approach conserves resources while still providing timely updates:
Add the React useEffect
hook and the usePollingTransactions
hook to the useTransactions
hook:
// src/hooks/useTransactions.ts
// Import React useEffect hook
import { useEffect, useState } from "react";
/**
* Hook to manage transaction data with polling that automatically
* activates only when pending transactions are detected
* @param wallet - Wallet address to fetch transactions for
* @returns Transaction data with loading and error states
*/
export function usePollingTransactions(wallet?: string) {
// Track if we have any pending transactions that need polling
const [hasPendingTx, setHasPendingTx] = useState(false);
const query = useQuery<Transaction[], Error>({
queryKey: ['transactions', wallet],
queryFn: async () => {
if (!wallet) throw new Error("Wallet is required");
const response = await fetch(`/api/escrow/transactions?wallet=${wallet}`);
return response.json();
},
enabled: !!wallet,
// Only activate polling when wallet is connected AND we have pending transactions
refetchInterval: wallet && hasPendingTx ? 5000 : false,
});
// Monitor transaction data for pending status changes
useEffect(() => {
if (query.data) {
// Check if any transactions have PENDING status
const isPending = query.data.some(tx => tx.status === TX_STATUS.PENDING);
// Only update state if the pending status changed
// This prevents unnecessary re-renders
if (isPending !== hasPendingTx) {
setHasPendingTx(isPending);
}
}
}, [query.data, hasPendingTx]);
return query;
}
7. Update MyTransactions Component
Finally, we need to update our MyTransactions
component src/components/MyTransactions.tsx
to use the new polling hook instead of the regular transactions hook:
// src/components/MyTransactions.tsx
"use client";
// Update this import to use the new polling hook instead
import { usePollingTransactions } from '@/hooks/useTransactions';
import { useWallet } from '@ada-anvil/weld/react';
import { Transaction, TransactionStatus, TX_STATUS } from '@/lib/types';
// Rest of component code remains the same...
export default function MyTransactions() {
const wallet = useWallet();
const address = wallet.changeAddressBech32;
// Replace useTransactionsByWallet with usePollingTransactions
const { data: transactions = [], error, isLoading } = usePollingTransactions(address);
// Rest of component remains the same...
}
This change enables smart polling that automatically activates when there are pending transactions, and disables when all transactions are confirmed, providing real-time updates while conserving resources.
8. Test the Webhook Integration
Let's test the complete flow with webhooks:
Ensure your Next.js server is running
Verify ngrok is properly exposing your local server
Check that the Blockfrost webhook is configured and enabled
Connect your wallet in the application
Lock some funds using the Lock Funds form
Observe how the transaction initially shows as "Pending"
Wait for Blockfrost to detect the transaction confirmation and send a webhook
Verify that the transaction status updates to "Confirmed" immediately after the webhook is received
Understanding the Webhook Flow
The complete flow with webhooks works as follows:
Transaction Creation: User locks funds, creating a transaction
Initial Status: Transaction is stored with "PENDING" status
Initial Polling: UI polls frequently at first to provide responsive feedback
Blockchain Confirmation: Transaction is confirmed on the Cardano blockchain
Webhook Notification: Blockfrost detects the confirmation and sends a webhook
Status Update: Our webhook handler updates the transaction status to "CONFIRMED"
UI Update: The next poll or React Query invalidation refreshes the UI
This hybrid approach combines the best of both worlds:
Immediate feedback through initial polling
Reliable long-term updates through webhooks
Fallback to extended polling for resilience
Monitoring Webhook Activity
To help troubleshoot and verify webhook activity, let's add some simple logging:
// src/app/api/webhooks/blockfrost/route.ts
// Add this to the existing file
// Add at the top of the POST function
console.log(`Received webhook at ${new Date().toISOString()}`);
// Add inside the try block after processing
console.log(`Processed ${body.payload.length} webhook events`);
// Add inside the catch block
console.error("Webhook error details:", JSON.stringify(err));
Webhook Security Considerations
In a production environment, you would want to add security to your webhook endpoint:
Webhook Secrets: Use a shared secret with Blockfrost to verify webhook authenticity
IP Filtering: Restrict access to known Blockfrost IP addresses
Rate Limiting: Protect against DoS attacks by implementing rate limiting
Request Validation: Validate the webhook payload structure before processing
For our tutorial, we've kept it simple, but consider these enhancements for production use.
Benefits of Real-time Updates
With the webhook implementation, users will experience:
Immediate Feedback: Transaction status changes are reflected immediately
Reduced Network Traffic: No need for constant polling requests
Better Battery Life: Mobile devices benefit from fewer background requests
More Responsive UI: Status changes happen in near real-time
Troubleshooting
Webhook Not Receiving Events
If your webhook isn't receiving events:
Check ngrok status - ensure the tunnel is active
Verify your webhook URL in the Blockfrost dashboard
Check Blockfrost webhook logs for delivery attempts
Ensure your application is properly handling POST requests
Inconsistent Status Updates
If transaction statuses are not updating correctly:
Enable detailed logging in your webhook handler
Verify the transaction hash matching in your database operations
Check that the database is successfully updated when webhooks are received
Best Practices for Webhook Implementation
Idempotency: Ensure your webhook handler can safely process the same event multiple times
Async Processing: For high-volume applications, consider processing webhooks asynchronously
Monitoring: Implement logging and monitoring to track webhook health
Fallback Strategy: Always maintain a fallback mechanism (like polling) for resilience
Congratulations! You've completed Part 5 of the guide. Your application now receives real-time updates when transactions are confirmed on the blockchain, providing a much better user experience.
Last updated
Was this helpful?