Skip to content

Webhooks Guide

Webhooks allow your application to receive real-time notifications when events occur in Solatis.

Why Use Webhooks?

Webhooks vs Polling

AspectWebhooksPolling
Real-time✅ Immediate❌ Delayed
Efficiency✅ No wasted requests❌ Wasteful
Latency✅ < 1 second❌ 5-30 seconds
Complexity⚠️ Moderate✅ Simple
Cost✅ Lower❌ Higher

Example: Processing transcriptions

  • Polling: Check every 5 seconds (288 requests/day per transcription)
  • Webhooks: Receive notification immediately (1 request)

Getting Started

Step 1: Create Webhook Endpoint

Your endpoint should accept POST requests:

Node.js (Express):

javascript
const express = require('express');
const app = express();

app.post('/webhooks/solatis', express.json(), (req, res) => {
  const event = req.body;

  console.log('Webhook received:', event.event);
  console.log('Event ID:', event.id);

  // Process the event
  handleSolatisEvent(event);

  // Acknowledge receipt
  res.status(200).send('OK');
});

app.listen(3000);

Python (Flask):

python
from flask import Flask, request

app = Flask(__name__)

@app.route('/webhooks/solatis', methods=['POST'])
def webhook():
    event = request.json

    print(f'Webhook received: {event["event"]}')
    print(f'Event ID: {event["id"]}')

    # Process the event
    handle_solatis_event(event)

    return 'OK', 200

if __name__ == '__main__':
    app.run()

Step 2: Make Your Endpoint Publicly Accessible

Your webhook endpoint must be accessible from the internet:

https://your-domain.com/webhooks/solatis

Testing locally: Use ngrok to expose your local development server

bash
ngrok http 3000
# ngrok exposes http://localhost:3000 as https://abc123.ngrok.io

Step 3: Register Webhook

Register your webhook endpoint via API:

bash
curl -X POST https://api.solatis.team/v2/webhooks \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-domain.com/webhooks/solatis",
    "events": ["document.analyzed", "transcription.completed"],
    "secret": "your-secret-for-signatures"
  }'

Or via Dashboard:

  1. Settings → Developer → Webhooks
  2. Click "Add Webhook"
  3. Enter URL and select events
  4. Copy webhook ID and secret

Webhook Events

Available Events

Document Events

EventFired WhenPayload
document.analyzedDocument analysis completesAnalysis results
document.deletedDocument is deletedDocument ID
document.sharedDocument is sharedShare details

Meeting Events

EventFired WhenPayload
transcription.startedTranscription beginsTranscription ID
transcription.completedTranscription finishesFull transcript
meeting.analyzedMeeting analysis completesAnalysis results
action_items.extractedAction items extractedAction items list

Task Events

EventFired WhenPayload
task.createdNew task createdTask details
task.updatedTask is updatedUpdated task
task.completedTask marked completeTask details
task.deletedTask is deletedTask ID

Generation Events

EventFired WhenPayload
content.generatedContent generation completesGenerated content
brainstorm.completedBrainstorming session endsResults

Example Webhook Payload

Document Analyzed

json
{
  "id": "evt_abc123xyz",
  "event": "document.analyzed",
  "created_at": "2026-02-02T10:30:00Z",
  "data": {
    "document_id": "doc_xyz789",
    "status": "completed",
    "summary": "This is a service agreement between Acme Corp and Client LLC...",
    "extracted_fields": {
      "parties": ["Acme Corp", "Client LLC"],
      "start_date": "2024-01-01",
      "end_date": "2025-01-01",
      "obligations": [...]
    },
    "confidence": 0.92
  }
}

Transcription Completed

json
{
  "id": "evt_def456",
  "event": "transcription.completed",
  "created_at": "2026-02-02T11:00:00Z",
  "data": {
    "transcription_id": "trans_abc123",
    "status": "completed",
    "transcript": "Full transcript text...",
    "speakers": [
      {
        "speaker": "Speaker 1",
        "segments": [...]
      },
      {
        "speaker": "Speaker 2",
        "segments": [...]
      }
    ],
    "duration_seconds": 1800,
    "confidence": 0.95
  }
}

Verify Webhook Signatures

Always verify webhook signatures to ensure requests come from Solatis.

How Signatures Work

  1. Solatis creates HMAC-SHA256 hash of the request body
  2. Sends hash in X-Solatis-Signature header
  3. Your endpoint verifies the signature matches

Verify Signature (JavaScript)

javascript
const crypto = require('crypto');

const verifySignature = (payload, signature, secret) => {
  const hmac = crypto.createHmac('sha256', secret);
  const digest = hmac.update(payload).digest('hex');
  return crypto.timingSafeEqual(signature, digest);
};

app.post('/webhooks/solatis', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-solatis-signature'];
  const payload = req.body;

  // Verify signature
  if (!verifySignature(payload.toString(), signature, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Unauthorized');
  }

  const event = JSON.parse(payload);

  // Process event
  handleEvent(event);

  res.status(200).send('OK');
});

Verify Signature (Python)

python
import hmac
import hashlib
from flask import request

@app.route('/webhooks/solatis', methods=['POST'])
def webhook():
    signature = request.headers.get('X-Solatis-Signature')
    payload = request.get_data()

    # Verify signature
    expected = hmac.new(
        os.getenv('WEBHOOK_SECRET').encode(),
        payload,
        hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(signature, expected):
        return 'Unauthorized', 401

    event = request.json

    # Process event
    handle_event(event)

    return 'OK', 200

Verify Signature (Go)

go
package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io"
	"net/http"
)

func verifySignature(payload []byte, signature string, secret string) bool {
	h := hmac.New(sha256.New, []byte(secret))
	h.Write(payload)
	expected := hex.EncodeToString(h.Sum(nil))
	return hmac.Equal([]byte(signature), []byte(expected))
}

func webhookHandler(w http.ResponseWriter, r *http.Request) {
	signature := r.Header.Get("X-Solatis-Signature")
	payload, _ := io.ReadAll(r.Body)

	if !verifySignature(payload, signature, os.Getenv("WEBHOOK_SECRET")) {
		w.WriteHeader(http.StatusUnauthorized)
		fmt.Fprintf(w, "Unauthorized")
		return
	}

	// Process event
	w.WriteHeader(http.StatusOK)
	fmt.Fprintf(w, "OK")
}

Webhook Best Practices

✅ Do

  1. Return 200 OK quickly

    javascript
    app.post('/webhooks', (req, res) => {
      // Acknowledge immediately
      res.status(200).send('OK');
    
      // Process asynchronously
      processEventAsync(req.body);
    });
  2. Process events asynchronously

    javascript
    // Add to queue for background processing
    taskQueue.add(event);
    res.status(200).send('OK');
  3. Verify signatures

    javascript
    if (!verifySignature(payload, signature, secret)) {
      return res.status(401).send('Unauthorized');
    }
  4. Log all events

    javascript
    console.log({
      timestamp: new Date(),
      event: event.event,
      id: event.id
    });
  5. Handle retries gracefully

    javascript
    // Solatis retries failed webhooks
    // Your endpoint should be idempotent
    const processed = await db.processed_events.findOne({ id: event.id });
    if (processed) return res.status(200).send('OK');

❌ Don't

  1. Don't make slow operations

    javascript
    // ❌ BAD - This might timeout
    app.post('/webhooks', async (req, res) => {
      await complexDatabaseOperations();
      res.status(200).send('OK');
    });
    
    // ✅ GOOD - Process in background
    app.post('/webhooks', (req, res) => {
      res.status(200).send('OK');
      complexDatabaseOperations();
    });
  2. Don't ignore verification

    javascript
    // ❌ BAD - No signature verification
    app.post('/webhooks', (req, res) => {
      handleEvent(req.body);
    });
    
    // ✅ GOOD - Always verify
    app.post('/webhooks', (req, res) => {
      if (!verifySignature(...)) return res.status(401).send();
      handleEvent(req.body);
    });
  3. Don't return errors to webhook

    javascript
    // ❌ BAD - Returns error
    app.post('/webhooks', (req, res) => {
      if (badData) return res.status(400).send('Bad data');
    });
    
    // ✅ GOOD - Log error, return 200
    app.post('/webhooks', (req, res) => {
      try {
        handleEvent(req.body);
      } catch (error) {
        console.error('Webhook error:', error);
      }
      res.status(200).send('OK');
    });
  4. Don't process synchronously

    • Webhooks should complete within 5 seconds
    • Move heavy operations to background jobs

Webhook Retries

If your endpoint returns non-2xx status code, Solatis retries:

Retry Schedule:

  • 1st retry: 1 minute
  • 2nd retry: 5 minutes
  • 3rd retry: 30 minutes
  • 4th retry: 2 hours
  • Final retry: 24 hours

After 5 failed attempts: Webhook is disabled

To re-enable disabled webhooks:

  1. Fix the issue
  2. Go to Settings → Developer → Webhooks
  3. Click "Re-enable" on the webhook

Monitoring Webhooks

View Webhook Deliveries

bash
curl https://api.solatis.team/v2/webhooks/{webhook_id}/deliveries \
  -H "Authorization: Bearer YOUR_API_KEY"

Response:

json
{
  "deliveries": [
    {
      "id": "del_abc123",
      "event": "document.analyzed",
      "status": "success",
      "status_code": 200,
      "delivered_at": "2026-02-02T10:30:00Z",
      "response_time_ms": 145
    },
    {
      "id": "del_def456",
      "event": "transcription.completed",
      "status": "failed",
      "status_code": 500,
      "error": "Internal server error",
      "retry_count": 2
    }
  ]
}

Check Webhook Health

bash
curl https://api.solatis.team/v2/webhooks/{webhook_id}/health \
  -H "Authorization: Bearer YOUR_API_KEY"

Webhook Scaling

As your application grows, webhook traffic increases. Tips for scaling:

  1. Use a queue

    javascript
    // Add to queue immediately
    app.post('/webhooks', (req, res) => {
      queue.add(req.body);
      res.status(200).send('OK');
    });
    
    // Process from queue with workers
    queue.process(async (job) => {
      handleEvent(job.data);
    });
  2. Add multiple webhook endpoints

    bash
    # Register multiple endpoints for load distribution
    POST /v2/webhooks
    {
      "url": "https://your-domain.com/webhooks/1",
      "events": [...]
    }
    
    POST /v2/webhooks
    {
      "url": "https://your-domain.com/webhooks/2",
      "events": [...]
    }
  3. Use idempotency keys

    javascript
    // Track processed events to avoid duplicate processing
    const key = `${event.id}:${event.created_at}`;
    if (await cache.get(key)) return res.status(200).send('OK');
    
    handleEvent(event);
    await cache.set(key, true, 86400); // 24 hours

Troubleshooting

Webhook Not Firing

Check:

  1. Webhook URL is publicly accessible
  2. URL returns 2xx status code
  3. Webhook is enabled (not disabled)
  4. Event filter matches events occurring
  5. Firewall/WAF not blocking requests

Getting Wrong Status Code

Ensure endpoint returns 200-299:

javascript
// ✅ GOOD - Returns 200
res.status(200).send('OK');

// ❌ BAD - Returns 201
res.status(201).send('Created');

// ❌ BAD - Returns 204 (no content, may cause issues)
res.status(204).send();

Signature Verification Failing

Debug:

javascript
// Log actual values
console.log('Signature header:', signature);
console.log('Secret:', secret);
console.log('Payload:', payload.toString());

// Calculate what you expect
const expected = hmac('sha256', secret, payload);
console.log('Expected:', expected);

Webhook Timing Out

Solutions:

  1. Return 200 OK immediately
  2. Move processing to background
  3. Increase server timeout limits
  4. Check server resources (CPU, memory)

Next Steps

Released under the MIT License.