Skip to main content

Prerequisites

Before setting up webhooks, make sure you have:
A Jasni API key (get one here)
A publicly accessible HTTPS endpoint
A way to store secrets securely (environment variables, secrets manager)

Step 1: Create Your Endpoint

Create an HTTP POST endpoint to receive webhook events. Here’s a minimal example:
const express = require('express');
const app = express();

// Use raw body for signature verification
app.post('/webhooks/jasni', 
  express.raw({ type: 'application/json' }), 
  (req, res) => {
    // Acknowledge immediately
    res.status(200).send('OK');
    
    // Parse and process
    const event = JSON.parse(req.body.toString());
    console.log('Received:', event.event);
    
    // Handle the event
    handleEvent(event);
  }
);

app.listen(3000);

Step 2: Register Your Webhook

Use the Jasni API to register your endpoint:
curl -X POST https://api.jasni.ai/api/v1/webhooks \
  -H "Authorization: Bearer jsk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.com/webhooks/jasni",
    "events": ["email.received", "email.sent"],
    "description": "Production webhook"
  }'
Save the secret immediately! The webhook secret is only shown once when creating the webhook. Store it securely in your environment variables.
The response will include:
{
  "success": true,
  "data": {
    "webhook": {
      "id": "wh_abc123",
      "url": "https://your-server.com/webhooks/jasni",
      "events": ["email.received", "email.sent"],
      "secret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
      "active": true,
      "description": "Production webhook"
    }
  }
}

Step 3: Add Signature Verification

Always verify that webhooks are genuinely from Jasni:
const crypto = require('crypto');

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

app.post('/webhooks/jasni', 
  express.raw({ type: 'application/json' }), 
  (req, res) => {
    const signature = req.headers['x-webhook-signature'];
    const payload = req.body.toString();
    
    if (!verifySignature(payload, signature, process.env.WEBHOOK_SECRET)) {
      return res.status(401).send('Invalid signature');
    }
    
    res.status(200).send('OK');
    
    const event = JSON.parse(payload);
    handleEvent(event);
  }
);

Step 4: Handle Events

Implement handlers for each event type you’ve subscribed to:
async function handleEvent(event) {
  switch (event.event) {
    case 'email.received':
      // New email arrived - trigger your AI agent
      await processIncomingEmail(event.data);
      break;
      
    case 'email.sent':
      // Email sent successfully - update your records
      await logSentEmail(event.data);
      break;
      
    case 'email.bounced':
      // Delivery failed - handle the bounce
      await handleBounce(event.data);
      break;
      
    case 'email.spam':
      // Email marked as spam
      await logSpamEmail(event.data);
      break;
      
    case 'email.deleted':
      // Email was deleted
      await syncDeletion(event.data);
      break;
      
    default:
      console.log('Unknown event:', event.event);
  }
}

Step 5: Test Your Integration

Local Development with ngrok

Use ngrok to expose your local server:
# Start your local server
npm run dev  # Running on http://localhost:3000

# In another terminal, expose it publicly
ngrok http 3000
Use the ngrok URL (e.g., https://abc123.ngrok.io/webhooks/jasni) when creating your test webhook.

Send a Test Event

Create a simple script to simulate webhook events:
const crypto = require('crypto');

const secret = process.env.WEBHOOK_SECRET;
const payload = JSON.stringify({
  event: 'email.received',
  timestamp: new Date().toISOString(),
  data: {
    id: 'test-123',
    from: '[email protected]',
    to: ['[email protected]'],
    subject: 'Test Email',
    account: '[email protected]',
    folder: 'INBOX',
    text: 'This is a test email for webhook testing.',
  },
});

const signature = crypto
  .createHmac('sha256', secret)
  .update(payload)
  .digest('hex');

// Send to your endpoint
fetch('http://localhost:3000/webhooks/jasni', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Webhook-Signature': signature,
    'X-Webhook-Timestamp': Math.floor(Date.now() / 1000).toString(),
    'X-Webhook-Event': 'email.received',
  },
  body: payload,
}).then(res => console.log('Response:', res.status));

Best Practices

Respond Quickly

Webhook requests timeout after 30 seconds. Return a 200 response immediately, then process asynchronously.
app.post('/webhooks/jasni', (req, res) => {
  // Acknowledge FIRST
  res.status(200).send('OK');
  
  // Process AFTER responding
  processEventAsync(req.body).catch(console.error);
});

Handle Duplicates

The same event might be delivered multiple times. Use idempotency:
const processedEvents = new Set(); // Use Redis in production

async function handleEvent(event) {
  const eventId = `${event.event}-${event.data.id}-${event.timestamp}`;
  
  if (processedEvents.has(eventId)) {
    console.log('Duplicate, skipping:', eventId);
    return;
  }
  
  processedEvents.add(eventId);
  await processEvent(event);
}

Use a Message Queue

For high-volume webhooks, queue events for processing:
import { Queue, Worker } from 'bullmq';

const webhookQueue = new Queue('webhooks');

// Endpoint adds to queue
app.post('/webhooks/jasni', async (req, res) => {
  await webhookQueue.add('process', req.body);
  res.status(200).send('OK');
});

// Worker processes queue
new Worker('webhooks', async (job) => {
  await processEvent(job.data);
});

Deployment Checklist

Use HTTPS - Never use HTTP in production
Verify signatures - Always validate X-Webhook-Signature
Store secrets securely - Use environment variables or a secrets manager
Handle duplicates - Implement idempotency
Respond quickly - Return 200 within 30 seconds
Log events - Track received webhooks for debugging
Monitor errors - Set up alerts for failures
Test thoroughly - Verify all event types work correctly

Troubleshooting

  • Verify your endpoint is publicly accessible
  • Check that the webhook is active (active: true)
  • Ensure you’re subscribed to the correct events
  • Check your server logs for incoming requests
  • Use the raw request body, not parsed JSON
  • Ensure you’re using the correct webhook secret
  • Check that the secret hasn’t been rotated
  • Jasni retries failed deliveries (10s, 60s, 300s delays)
  • Implement idempotency to handle duplicates
  • Ensure your endpoint responds with 200 quickly
  • The secret is only shown once at creation
  • Delete the webhook and create a new one
  • Save the new secret immediately

Next Steps