Skip to main content

Overview

Webhooks let you receive real-time notifications when transactions are confirmed or fail on-chain. Instead of polling the transactions endpoint, register a URL and Camino will POST signed payloads to it.

Available Events

EventDescription
transaction.confirmedA tracked transaction was confirmed on-chain
transaction.failedA tracked transaction failed on-chain

1. Create a Webhook

Register a webhook endpoint with the events you want to subscribe to:
const response = await fetch('https://api.caminotreasury.com/v1/webhooks', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-api-key': 'your-api-key'
  },
  body: JSON.stringify({
    url: 'https://your-app.com/webhooks/camino',
    events: ['transaction.confirmed', 'transaction.failed'],
    wallets: ['0x742d35cc6634c0532925a3b844bc454e4438f44e'] // optional
  })
});

const { webhook } = await response.json();

// {
//   "id": 1,
//   "url": "https://your-app.com/webhooks/camino",
//   "events": ["transaction.confirmed", "transaction.failed"],
//   "wallets": ["0x742d35cc6634c0532925a3b844bc454e4438f44e"],
//   "secret": "whsec_eb77b74472e0e4750b41d9c694dd02ca768bae68...",
//   "createdAt": "2024-01-15T10:30:00.000Z"
// }
FieldTypeDescription
urlstringYour endpoint URL (must be HTTPS)
eventsstring[]Events to subscribe to (at least one required)
walletsstring[]Optional wallet filter — if omitted, fires for all wallets
The secret is only returned when the webhook is created. Store it securely — you’ll need it to verify incoming signatures.

2. List Webhooks

Fetch all registered webhooks:
const response = await fetch('https://api.caminotreasury.com/v1/webhooks', {
  headers: { 'x-api-key': 'your-api-key' }
});

const { data: webhooks, count } = await response.json();

// webhooks:
// [
//   {
//     "id": 1,
//     "url": "https://your-app.com/webhooks/camino",
//     "events": ["transaction.confirmed", "transaction.failed"],
//     "wallets": ["0x742d35cc6634c0532925a3b844bc454e4438f44e"],
//     "createdAt": "2024-01-15T10:30:00.000Z"
//   }
// ]
The secret is not included in the list response for security. If you lose it, delete the webhook and create a new one.

3. Delete a Webhook

Remove a webhook by its ID:
const response = await fetch('https://api.caminotreasury.com/v1/webhooks/1', {
  method: 'DELETE',
  headers: { 'x-api-key': 'your-api-key' }
});

const result = await response.json();
// { deleted: true }

4. Webhook Payload Format

When an event fires, Camino sends a POST request to your URL with this body:
{
  "event": "transaction.confirmed",
  "timestamp": "2024-01-15T10:35:00.000Z",
  "data": {
    "hash": "0x1234567890abcdef...",
    "chainId": 1,
    "status": "confirmed",
    "wallet": "0x742d35cc6634c0532925a3b844bc454e4438f44e"
  }
}
The request includes an X-Webhook-Signature header for verification.

5. Verify Webhook Signatures

Every webhook delivery is signed with your secret using HMAC-SHA256. Verify the signature to ensure the payload was sent by Camino. The X-Webhook-Signature header has the format:
t={timestamp},v1={signature}
To verify:
import { createHmac } from 'crypto';

function verifyWebhook(body, header, secret) {
  // Parse the signature header
  const parts = Object.fromEntries(
    header.split(',').map(p => p.split('='))
  );
  const timestamp = parts.t;
  const signature = parts.v1;

  // Strip the whsec_ prefix from the secret
  const rawSecret = secret.replace(/^whsec_/, '');

  // Compute expected signature: HMAC-SHA256(secret, "timestamp.body")
  const expected = createHmac('sha256', rawSecret)
    .update(`${timestamp}.${body}`)
    .digest('hex');

  // Compare signatures
  return signature === expected;
}

// In your webhook handler:
app.post('/webhooks/camino', (req, res) => {
  const body = req.body; // raw string body
  const header = req.headers['x-webhook-signature'];
  const secret = process.env.CAMINO_WEBHOOK_SECRET;

  if (!verifyWebhook(body, header, secret)) {
    return res.status(401).send('Invalid signature');
  }

  const payload = JSON.parse(body);
  console.log(`Event: ${payload.event}`, payload.data);
  res.status(200).send('OK');
});

Next Steps