Webhooks Guide
Webhooks allow your application to receive real-time notifications when events occur in Solatis.
Why Use Webhooks?
Webhooks vs Polling
| Aspect | Webhooks | Polling |
|---|---|---|
| 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):
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):
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/solatisTesting locally: Use ngrok to expose your local development server
ngrok http 3000
# ngrok exposes http://localhost:3000 as https://abc123.ngrok.ioStep 3: Register Webhook
Register your webhook endpoint via API:
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:
- Settings → Developer → Webhooks
- Click "Add Webhook"
- Enter URL and select events
- Copy webhook ID and secret
Webhook Events
Available Events
Document Events
| Event | Fired When | Payload |
|---|---|---|
document.analyzed | Document analysis completes | Analysis results |
document.deleted | Document is deleted | Document ID |
document.shared | Document is shared | Share details |
Meeting Events
| Event | Fired When | Payload |
|---|---|---|
transcription.started | Transcription begins | Transcription ID |
transcription.completed | Transcription finishes | Full transcript |
meeting.analyzed | Meeting analysis completes | Analysis results |
action_items.extracted | Action items extracted | Action items list |
Task Events
| Event | Fired When | Payload |
|---|---|---|
task.created | New task created | Task details |
task.updated | Task is updated | Updated task |
task.completed | Task marked complete | Task details |
task.deleted | Task is deleted | Task ID |
Generation Events
| Event | Fired When | Payload |
|---|---|---|
content.generated | Content generation completes | Generated content |
brainstorm.completed | Brainstorming session ends | Results |
Example Webhook Payload
Document Analyzed
{
"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
{
"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
- Solatis creates HMAC-SHA256 hash of the request body
- Sends hash in
X-Solatis-Signatureheader - Your endpoint verifies the signature matches
Verify Signature (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)
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', 200Verify Signature (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
Return 200 OK quickly
javascriptapp.post('/webhooks', (req, res) => { // Acknowledge immediately res.status(200).send('OK'); // Process asynchronously processEventAsync(req.body); });Process events asynchronously
javascript// Add to queue for background processing taskQueue.add(event); res.status(200).send('OK');Verify signatures
javascriptif (!verifySignature(payload, signature, secret)) { return res.status(401).send('Unauthorized'); }Log all events
javascriptconsole.log({ timestamp: new Date(), event: event.event, id: event.id });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
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(); });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); });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'); });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:
- Fix the issue
- Go to Settings → Developer → Webhooks
- Click "Re-enable" on the webhook
Monitoring Webhooks
View Webhook Deliveries
curl https://api.solatis.team/v2/webhooks/{webhook_id}/deliveries \
-H "Authorization: Bearer YOUR_API_KEY"Response:
{
"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
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:
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); });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": [...] }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:
- Webhook URL is publicly accessible
- URL returns 2xx status code
- Webhook is enabled (not disabled)
- Event filter matches events occurring
- Firewall/WAF not blocking requests
Getting Wrong Status Code
Ensure endpoint returns 200-299:
// ✅ 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:
// 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:
- Return 200 OK immediately
- Move processing to background
- Increase server timeout limits
- Check server resources (CPU, memory)
Next Steps
- Code Examples - Real-world webhook examples
- API Reference - All endpoints
- Rate Limiting - Webhook rate limits