Skip to main content

Webhooks

Webhooks enable real-time notifications when events occur in your AxonVault account, such as transaction confirmations or failures.

Supported Events

EventDescription
transaction.pendingTransaction submitted to network
transaction.confirmedTransaction confirmed on chain
transaction.failedTransaction failed or reverted
approval.requestedTransaction requires approval
approval.approvedTransaction approved
approval.rejectedTransaction rejected

Creating a Webhook

POST /v1/tx-relay/webhooks
{
  "tenantId": "ten_abc123",
  "projectId": "proj_abc123",
  "url": "https://yourapp.com/webhooks/axonvault",
  "events": ["transaction.confirmed", "transaction.failed"],
  "secret": "whsec_abc123xyz789",
  "enabled": true
}
Response:
{
  "config": {
    "webhookId": "wh_abc123",
    "url": "https://yourapp.com/webhooks/axonvault",
    "events": ["transaction.confirmed", "transaction.failed"],
    "enabled": true,
    "createAt": "2024-01-15T10:30:00Z"
  }
}

Webhook Payload

{
  "event": "transaction.confirmed",
  "timestamp": "2024-01-15T10:35:00Z",
  "data": {
    "txId": "tx_abc123",
    "txHash": "0x1234567890abcdef...",
    "status": 2,
    "fromAddress": "0x742d35Cc...",
    "toAddress": "0x1234567890...",
    "amount": "1000000000000000000",
    "chainReference": "eip155:1",
    "blockNumber": 12345678,
    "confirmations": 12
  }
}

Verifying Webhooks

Always verify webhook signatures to ensure authenticity:
import crypto from 'crypto';

function verifyWebhook(payload, signature, secret) {
  const expectedSig = crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(payload))
    .digest('hex');
  
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSig)
  );
}

// Express.js handler
app.post('/webhooks/axonvault', express.json(), (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  
  if (!verifyWebhook(req.body, signature, process.env.WEBHOOK_SECRET)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
  
  // Process webhook
  const { event, data } = req.body;
  console.log(`Received ${event}:`, data);
  
  res.status(200).json({ received: true });
});

Handling Events

async function handleWebhook(event, data) {
  switch (event) {
    case 'transaction.confirmed':
      await handleConfirmed(data);
      break;
    case 'transaction.failed':
      await handleFailed(data);
      break;
    case 'approval.requested':
      await notifyApprovers(data);
      break;
  }
}

async function handleConfirmed(data) {
  // Update your database
  await db.transactions.update({
    where: { txId: data.txId },
    data: {
      status: 'confirmed',
      txHash: data.txHash,
      blockNumber: data.blockNumber
    }
  });
  
  // Notify user
  await sendNotification(data.userId, {
    title: 'Transaction Confirmed',
    body: `Your transaction was confirmed in block ${data.blockNumber}`
  });
}

async function handleFailed(data) {
  // Update database
  await db.transactions.update({
    where: { txId: data.txId },
    data: { status: 'failed', error: data.error }
  });
  
  // Alert operations team
  await alertOps(`Transaction ${data.txId} failed: ${data.error}`);
}

Retry Policy

AxonVault retries failed webhook deliveries with exponential backoff:
AttemptDelay
1Immediate
21 minute
35 minutes
430 minutes
52 hours
After 5 failed attempts, the webhook is marked as failed.

Viewing Webhook History

GET /v1/tx-relay/webhooks/{webhookId}/history
Response:
{
  "history": [
    {
      "messageId": "msg_abc123",
      "eventType": "transaction.confirmed",
      "statusCode": 200,
      "success": true,
      "sentAt": "2024-01-15T10:35:00Z",
      "retryCount": "0"
    },
    {
      "messageId": "msg_def456",
      "eventType": "transaction.failed",
      "statusCode": 500,
      "success": false,
      "errorMessage": "Connection timeout",
      "sentAt": "2024-01-15T10:40:00Z",
      "retryCount": "2"
    }
  ]
}

Best Practices

Return a 200 response within 5 seconds. Process events asynchronously:
app.post('/webhooks', (req, res) => {
  // Acknowledge immediately
  res.status(200).send('OK');
  
  // Process async
  processWebhook(req.body).catch(console.error);
});
Webhooks may be delivered multiple times. Use messageId for idempotency:
async function processWebhook(payload) {
  const { messageId } = payload;
  
  // Check if already processed
  if (await cache.get(`webhook:${messageId}`)) {
    return;
  }
  
  // Process
  await handleEvent(payload);
  
  // Mark as processed
  await cache.set(`webhook:${messageId}`, true, 86400);
}
Set up alerts for webhook failures:
  • Monitor HTTP status codes
  • Track retry counts
  • Alert on repeated failures