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
| Event | Description |
|---|
transaction.confirmed | A tracked transaction was confirmed on-chain |
transaction.failed | A 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"
// }
| Field | Type | Description |
|---|
url | string | Your endpoint URL (must be HTTPS) |
events | string[] | Events to subscribe to (at least one required) |
wallets | string[] | 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 }
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