Webhook Integration Guide
Send real-time feedback events to your own endpoints. Perfect for custom integrations, automation workflows, and connecting Miniback to your existing systems.
⚡ Quick Setup (2 minutes)
- Prepare an HTTPS endpoint that accepts POST requests
- In Miniback: Settings → Notifications → Add Webhook
- Enter your webhook URL
- Click “Test” to verify
- Save to start receiving events
Requirements: HTTPS endpoint • Accepts JSON POST requests
Overview
Webhooks let you receive instant notifications when feedback events occur. Miniback sends HTTP POST requests to your configured endpoints with event data in JSON format.
Use cases:
- Custom notification systems
- Integration with internal tools
- Triggering automation workflows
- Syncing feedback to other databases
- Custom analytics and reporting
Webhook Payload
Event Types
Miniback sends two types of webhook events:
1. Feedback Created (feedback.created)
Sent when new feedback is submitted via widget or API.
2. Feedback Updated (feedback.updated)
Sent when feedback status changes (NEW → REVIEWED → RESOLVED).
Payload Structure
{
"event": "feedback.created",
"timestamp": "2025-10-31T12:34:56.789Z",
"data": {
"feedback": {
"id": "clx123abc456",
"message": "The submit button is not working on mobile",
"status": "NEW",
"createdAt": "2025-10-31T12:34:56.789Z"
},
"project": {
"id": "clx789xyz123",
"name": "My Website",
"slug": "my-website"
}
}
}Feedback Updated Payload
When feedback status changes, the payload includes the previous status:
{
"event": "feedback.updated",
"timestamp": "2025-10-31T13:45:00.000Z",
"data": {
"feedback": {
"id": "clx123abc456",
"message": "The submit button is not working on mobile",
"status": "REVIEWED",
"createdAt": "2025-10-31T12:34:56.789Z"
},
"project": {
"id": "clx789xyz123",
"name": "My Website",
"slug": "my-website"
},
"previousStatus": "NEW"
}
}Field Reference
| Field | Type | Description |
|---|---|---|
event | string | Event type: feedback.created or feedback.updated |
timestamp | string | ISO 8601 timestamp when event was triggered |
data.feedback.id | string | Unique feedback identifier |
data.feedback.message | string | Feedback message content |
data.feedback.status | string | Current status: NEW, REVIEWED, or RESOLVED |
data.feedback.createdAt | string | ISO 8601 timestamp of feedback creation |
data.project.id | string | Project identifier |
data.project.name | string | Human-readable project name |
data.project.slug | string | URL-friendly project identifier |
data.previousStatus | string | Previous status (only for feedback.updated events) |
Setting Up Your Endpoint
Endpoint Requirements
Your webhook endpoint must:
- Accept POST requests with
Content-Type: application/json - Use HTTPS (HTTP allowed for localhost testing only)
- Respond within 5 seconds with status code 2xx
- Handle retries gracefully (Miniback retries on failure)
Example Implementations
Node.js (Express)
const express = require("express");
const app = express();
app.use(express.json());
app.post("/webhooks/miniback", (req, res) => {
const { event, timestamp, data } = req.body;
console.log(`Received ${event} at ${timestamp}`);
console.log("Feedback:", data.feedback);
console.log("Project:", data.project);
// Process the webhook data
// ... your custom logic here ...
// Respond with 200 OK
res.status(200).json({ received: true });
});
app.listen(3000, () => {
console.log("Webhook server running on port 3000");
});Python (Flask)
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/webhooks/miniback', methods=['POST'])
def handle_webhook():
data = request.json
event = data.get('event')
timestamp = data.get('timestamp')
feedback = data.get('data', {}).get('feedback', {})
project = data.get('data', {}).get('project', {})
print(f"Received {event} at {timestamp}")
print(f"Feedback: {feedback}")
print(f"Project: {project}")
# Process the webhook data
# ... your custom logic here ...
return jsonify({'received': True}), 200
if __name__ == '__main__':
app.run(port=3000)Next.js (App Router)
// app/api/webhooks/miniback/route.ts
import { NextRequest, NextResponse } from "next/server";
export async function POST(request: NextRequest) {
const body = await request.json();
const { event, timestamp, data } = body;
console.log(`Received ${event} at ${timestamp}`);
console.log("Feedback:", data.feedback);
console.log("Project:", data.project);
// Process the webhook data
// ... your custom logic here ...
return NextResponse.json({ received: true }, { status: 200 });
}Configuring Webhooks in Miniback
1. Navigate to Settings
- Sign in to your Miniback dashboard
- Go to Settings → Notifications
- Find the Webhook Notifications card
2. Add Webhook URL
- Click “Add Webhook”
- Enter your endpoint URL (e.g.,
https://api.myapp.com/webhooks/miniback) - Click “Test” to send a test event
- If successful, click “Add” to save
3. Configure Event Topics
By default, webhooks receive all event types. To customize:
- Click the expand arrow on your webhook
- Check/uncheck event topics:
- New Feedback: Receive
feedback.createdevents - Status Changed: Receive
feedback.updatedevents
- New Feedback: Receive
- Changes are saved automatically
4. Test Your Webhook
The test event sends this payload:
{
"event": "test",
"timestamp": "2025-10-31T12:34:56.789Z",
"data": {
"message": "Test webhook notification from Miniback",
"user": "user@example.com"
}
}Your endpoint should respond with 2xx status code to pass the test.
Security Best Practices
1. Verify Webhook Signatures
All webhook requests include an HMAC-SHA256 signature in the X-Miniback-Signature header. Always verify this signature to ensure requests are from Miniback.
How Signature Verification Works
- Miniback generates a signature using your webhook secret and the request body
- The signature is sent in the
X-Miniback-Signatureheader - Your endpoint recalculates the signature and compares it
- Reject requests with invalid signatures
Verification Examples
Node.js (Express):
const crypto = require("crypto");
function verifySignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac("sha256", secret)
.update(payload, "utf8")
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
app.post(
"/webhooks/miniback",
express.raw({ type: "application/json" }),
(req, res) => {
const signature = req.headers["x-miniback-signature"];
const secret = process.env.WEBHOOK_SECRET; // Your webhook secret
if (!verifySignature(req.body, signature, secret)) {
console.error("Invalid signature");
return res.status(401).send("Unauthorized");
}
// Signature valid - process webhook
const payload = JSON.parse(req.body);
console.log("Verified webhook:", payload);
res.status(200).json({ received: true });
}
);Python (Flask):
import hmac
import hashlib
def verify_signature(payload, signature, secret):
expected_signature = hmac.new(
secret.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected_signature)
@app.route('/webhooks/miniback', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Miniback-Signature')
secret = os.environ['WEBHOOK_SECRET']
payload = request.get_data(as_text=True)
if not verify_signature(payload, signature, secret):
return jsonify({'error': 'Invalid signature'}), 401
# Signature valid - process webhook
data = request.json
print(f"Verified webhook: {data}")
return jsonify({'received': True}), 200Next.js (App Router):
import { NextRequest, NextResponse } from "next/server";
import crypto from "crypto";
function verifySignature(
payload: string,
signature: string,
secret: string
): boolean {
const expectedSignature = crypto
.createHmac("sha256", secret)
.update(payload, "utf8")
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
export async function POST(request: NextRequest) {
const signature = request.headers.get("x-miniback-signature");
const secret = process.env.WEBHOOK_SECRET!;
const payload = await request.text();
if (!signature || !verifySignature(payload, signature, secret)) {
return NextResponse.json({ error: "Invalid signature" }, { status: 401 });
}
// Signature valid - process webhook
const data = JSON.parse(payload);
console.log("Verified webhook:", data);
return NextResponse.json({ received: true });
}Important Notes:
- Always use
crypto.timingSafeEqual()orhmac.compare_digest()for comparison - Never use
===or==to compare signatures (vulnerable to timing attacks) - Store your webhook secret securely (environment variables, secret manager)
- The signature is calculated on the raw request body, not parsed JSON
2. Validate Payloads
Always validate incoming webhook data:
function isValidWebhook(body) {
return (
body &&
["feedback.created", "feedback.updated"].includes(body.event) &&
body.timestamp &&
body.data &&
body.data.feedback &&
body.data.project
);
}3. Rate Limiting
Implement rate limiting to prevent abuse:
const rateLimit = require("express-rate-limit");
const webhookLimiter = rateLimit({
windowMs: 1 * 60 * 1000, // 1 minute
max: 100, // limit to 100 requests per minute
});
app.post("/webhooks/miniback", webhookLimiter, handleWebhook);4. Use HTTPS
Always use HTTPS in production. Miniback will reject HTTP URLs (except localhost for testing).
Error Handling & Retries
Retry Behavior
If your endpoint returns an error (non-2xx status) or times out:
- Miniback will retry up to 3 times
- Retry delays: 1s, 5s, 15s
- After 3 failures, the event is dropped
Responding to Webhooks
✅ Good Response:
res.status(200).json({ received: true });✅ Also Good:
res.status(204).end(); // No content❌ Bad Response (triggers retry):
res.status(500).json({ error: "Database error" });Handling Failures Gracefully
Log failures for debugging:
app.post("/webhooks/miniback", async (req, res) => {
try {
await processWebhook(req.body);
res.status(200).json({ received: true });
} catch (error) {
console.error("Webhook processing failed:", error);
// Still return 200 to prevent retries
res.status(200).json({ received: true, error: error.message });
}
});Advanced Use Cases
1. Discord Notifications
Forward feedback to Discord:
app.post("/webhooks/miniback", async (req, res) => {
const { data } = req.body;
const { feedback, project } = data;
await fetch(DISCORD_WEBHOOK_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
content: `**New Feedback on ${project.name}**\n${feedback.message}`,
}),
});
res.status(200).json({ received: true });
});2. Database Sync
Store feedback in your database:
app.post("/webhooks/miniback", async (req, res) => {
const { event, data } = req.body;
if (event === "feedback.created") {
await db.feedback.create({
data: {
minibackId: data.feedback.id,
message: data.feedback.message,
status: data.feedback.status,
projectName: data.project.name,
},
});
}
res.status(200).json({ received: true });
});3. SMS Alerts
Send SMS for urgent feedback:
app.post("/webhooks/miniback", async (req, res) => {
const { data } = req.body;
const { feedback, project } = data;
if (feedback.message.toLowerCase().includes("urgent")) {
await twilioClient.messages.create({
body: `Urgent feedback on ${project.name}: ${feedback.message}`,
to: ADMIN_PHONE,
from: TWILIO_PHONE,
});
}
res.status(200).json({ received: true });
});4. Ticket Creation
Auto-create tickets in your issue tracker:
app.post("/webhooks/miniback", async (req, res) => {
const { data } = req.body;
const { feedback, project } = data;
await jiraClient.issues.createIssue({
fields: {
project: { key: "FEEDBACK" },
summary: `New feedback: ${feedback.message.substring(0, 50)}...`,
description: `${feedback.message}\n\nProject: ${project.name}\nID: ${feedback.id}`,
issuetype: { name: "Task" },
},
});
res.status(200).json({ received: true });
});Managing Webhooks
Multiple Webhooks
You can configure multiple webhook URLs to send events to different systems simultaneously.
Enable/Disable
Toggle webhooks on/off without deleting them:
- Go to Settings → Notifications
- Use the toggle switch next to each webhook
- Disabled webhooks are saved but won’t receive events
Remove Webhook
To permanently delete a webhook:
- Click the trash icon next to the webhook
- Confirm deletion
- Miniback will stop sending events to that URL
Troubleshooting
Webhook Not Receiving Events
Check 1: URL Accessibility
- Verify your endpoint is publicly accessible
- Test with curl:
curl -X POST https://your-endpoint.com
Check 2: HTTPS Required
- Ensure URL starts with
https://(nothttp://) - Exception:
http://localhostallowed for testing
Check 3: Response Time
- Endpoint must respond within 5 seconds
- Check server logs for timeouts
Check 4: Webhook Enabled
- Verify toggle is ON in Settings → Notifications
Test Works But Real Events Don’t
- Submit test feedback via your widget
- Check dashboard - does feedback appear?
- If NO: Widget issue
- If YES: Check webhook configuration
- Verify webhook topics include the event type
Debugging Tips
Log all incoming webhooks:
app.post("/webhooks/miniback", (req, res) => {
console.log("Webhook received:", JSON.stringify(req.body, null, 2));
// ... process webhook ...
});Check HTTP headers:
app.post("/webhooks/miniback", (req, res) => {
console.log("Headers:", req.headers);
console.log("User-Agent:", req.headers["user-agent"]); // Miniback-Webhook/1.0
});HTTP Headers
Miniback sends these headers with webhook requests:
POST /webhooks/miniback HTTP/1.1
Host: your-endpoint.com
Content-Type: application/json
User-Agent: Miniback-Webhook/1.0
X-Miniback-Signature: abc123def456...
X-Miniback-Timestamp: 2025-10-31T12:34:56.789Z
Content-Length: 456Header Reference
| Header | Description |
|---|---|
Content-Type | Always application/json |
User-Agent | Always Miniback-Webhook/1.0 |
X-Miniback-Signature | HMAC-SHA256 signature for verification |
X-Miniback-Timestamp | ISO 8601 timestamp of the event |
You can identify Miniback webhooks by checking for:
req.headers["user-agent"] === "Miniback-Webhook/1.0";What’s Next?
- Email Notifications - Set up email alerts
- Slack Integration - Send to Slack
- API Documentation - Learn about the Miniback API
Need help? See Troubleshooting or contact support through your dashboard.