@kyuzan/mountain-webhook-sdk
v1.0.0
Published
Webhook signature verification SDK for MOUNTAIN platform
Readme
@kyuzan/mountain-webhook-sdk
Secure webhook signature verification SDK for MOUNTAIN platform events.
Installation
npm install @kyuzan/mountain-webhook-sdk
# or
yarn add @kyuzan/mountain-webhook-sdk
# or
pnpm add @kyuzan/mountain-webhook-sdkQuick Start
import { MountainWebhookSdk } from '@kyuzan/mountain-webhook-sdk';
// Initialize the SDK
const sdk = MountainWebhookSdk.initialize();
// Verify webhook signature and get event data
const result = sdk.getEventFromRequest(
request, // Your webhook request object
'whsec_your_webhook_secret_here' // Webhook secret from MOUNTAIN dashboard
);
if (result.isValid) {
console.log('Event verified successfully:', result.payload);
// Process the event
} else {
console.error('Verification failed:', result.error);
}Features
- ✅ Secure signature verification using HMAC-SHA256
- ✅ Timestamp validation to prevent replay attacks
- ✅ TypeScript support with full type definitions
- ✅ Framework agnostic - works with any Node.js framework
- ✅ Easy integration with Express, Fastify, Next.js, and more
Usage Examples
Express.js
import express from 'express';
import { MountainWebhookSdk } from '@kyuzan/mountain-webhook-sdk';
const app = express();
const sdk = MountainWebhookSdk.initialize();
// Important: Use raw body parser for webhook endpoints
app.use('/webhook', express.raw({ type: 'application/json' }));
app.post('/webhook', (req, res) => {
const webhookSecret = process.env.MOUNTAIN_WEBHOOK_SECRET!;
const result = sdk.getEventFromRequest(
{
headers: req.headers,
body: req.body.toString(), // Convert buffer to string
},
webhookSecret
);
if (result.isValid) {
console.log('Event received:', result.payload);
// Process the event based on the data
handleEvent(result.payload);
res.status(200).send('OK');
} else {
console.error('Webhook verification failed:', result.error);
res.status(400).send('Invalid signature');
}
});
function handleEvent(payload: any) {
// Your event processing logic here
console.log('Processing event:', {
id: payload.id,
transactionHash: payload.transactionHash,
blockNumber: payload.blockNumber,
event: payload.event,
});
}Next.js API Route
// pages/api/webhook.ts or app/api/webhook/route.ts
import { NextRequest } from 'next/server';
import { MountainWebhookSdk } from '@kyuzan/mountain-webhook-sdk';
const sdk = MountainWebhookSdk.initialize();
export async function POST(request: NextRequest) {
try {
const body = await request.text(); // Get raw body as string
const webhookSecret = process.env.MOUNTAIN_WEBHOOK_SECRET!;
const result = sdk.getEventFromRequest(
{
headers: Object.fromEntries(request.headers),
body,
},
webhookSecret
);
if (result.isValid) {
// Process the verified event
await processEvent(result.payload);
return new Response('OK', { status: 200 });
} else {
console.error('Webhook verification failed:', result.error);
return new Response('Invalid signature', { status: 400 });
}
} catch (error) {
console.error('Webhook processing error:', error);
return new Response('Internal server error', { status: 500 });
}
}
async function processEvent(payload: any) {
// Your event processing logic
console.log('Event processed:', payload);
}Fastify
import Fastify from 'fastify';
import { MountainWebhookSdk } from '@kyuzan/mountain-webhook-sdk';
const fastify = Fastify();
const sdk = MountainWebhookSdk.initialize();
fastify.addContentTypeParser('application/json', { parseAs: 'string' },
(req, body, done) => {
done(null, body);
}
);
fastify.post('/webhook', async (request, reply) => {
const webhookSecret = process.env.MOUNTAIN_WEBHOOK_SECRET!;
const result = sdk.getEventFromRequest(
{
headers: request.headers,
body: request.body as string,
},
webhookSecret
);
if (result.isValid) {
console.log('Event verified:', result.payload);
reply.status(200).send('OK');
} else {
console.error('Verification failed:', result.error);
reply.status(400).send('Invalid signature');
}
});API Reference
MountainWebhookSdk.initialize(options?)
Initialize the SDK with optional configuration.
Parameters:
options(optional): SDK configuration options
Returns: MountainWebhookSdk instance
sdk.getEventFromRequest(request, webhookSecret, toleranceInSeconds?)
Verify webhook signature and extract event payload.
Parameters:
request: Object containing headers and bodyheaders: Request headers objectbody: Raw request body as string
webhookSecret: Webhook secret from MOUNTAIN dashboard (starts withwhsec_)toleranceInSeconds(optional): Timestamp tolerance in seconds (default: 300)
Returns: EventVerificationResult
type EventVerificationResult = {
isValid: boolean;
error?: string;
payload?: EventPayload;
};
type EventPayload = {
id: string;
event: object; // Decoded event log
transactionHash: string;
blockNumber: number;
transactionIndex: number;
logIndex: number;
blockTimestamp: number;
};Security Best Practices
1. Always verify signatures
Never skip signature verification in production:
const result = sdk.getEventFromRequest(request, webhookSecret);
if (!result.isValid) {
// Always reject invalid signatures
return res.status(400).send('Invalid signature');
}2. Use raw body parser
Webhook signatures are calculated on the raw request body:
// ✅ Correct - raw body parser
app.use('/webhook', express.raw({ type: 'application/json' }));
// ❌ Wrong - JSON parser modifies the body
app.use('/webhook', express.json());3. Set appropriate timestamp tolerance
The default tolerance is 5 minutes, but you can adjust it:
// More strict (1 minute)
const result = sdk.getEventFromRequest(request, secret, 60);
// More lenient (10 minutes)
const result = sdk.getEventFromRequest(request, secret, 600);4. Secure your webhook secret
Store your webhook secret securely using environment variables:
// ✅ Good
const webhookSecret = process.env.MOUNTAIN_WEBHOOK_SECRET;
// ❌ Bad - never hardcode secrets
const webhookSecret = 'whsec_hardcoded_secret';Error Handling
The SDK returns detailed error messages for debugging:
const result = sdk.getEventFromRequest(request, webhookSecret);
if (!result.isValid) {
switch (result.error) {
case 'No signature found in request':
// Missing X-Mountain-Signature header
break;
case 'Invalid signature format':
// Malformed signature header
break;
case 'Signature timestamp is outside of tolerance window':
// Request too old or too new
break;
case 'Invalid webhook secret':
// Webhook secret format is incorrect
break;
case 'Signature verification failed':
// Signature doesn't match
break;
default:
// Other errors (JSON parsing, etc.)
break;
}
}Getting Webhook Secrets
- Log in to the MOUNTAIN Dashboard
- Navigate to your project settings
- Go to the "Webhooks" section
- Copy your webhook secret (starts with
whsec_)
TypeScript Support
This package includes full TypeScript definitions:
import type {
EventVerificationResult,
EventPayload
} from '@kyuzan/mountain-webhook-sdk';Documentation
For more information about MOUNTAIN webhooks and event types, visit:
License
MIT
Support
For support and questions, please visit MOUNTAIN Documentation or create an issue on GitHub.
