Skip to Content
User GuidesWebhook Integration Guide

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)

  1. Prepare an HTTPS endpoint that accepts POST requests
  2. In Miniback: Settings → Notifications → Add Webhook
  3. Enter your webhook URL
  4. Click “Test” to verify
  5. 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

FieldTypeDescription
eventstringEvent type: feedback.created or feedback.updated
timestampstringISO 8601 timestamp when event was triggered
data.feedback.idstringUnique feedback identifier
data.feedback.messagestringFeedback message content
data.feedback.statusstringCurrent status: NEW, REVIEWED, or RESOLVED
data.feedback.createdAtstringISO 8601 timestamp of feedback creation
data.project.idstringProject identifier
data.project.namestringHuman-readable project name
data.project.slugstringURL-friendly project identifier
data.previousStatusstringPrevious status (only for feedback.updated events)

Setting Up Your Endpoint

Endpoint Requirements

Your webhook endpoint must:

  1. Accept POST requests with Content-Type: application/json
  2. Use HTTPS (HTTP allowed for localhost testing only)
  3. Respond within 5 seconds with status code 2xx
  4. 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

  1. Sign in to your Miniback dashboard
  2. Go to SettingsNotifications
  3. Find the Webhook Notifications card

2. Add Webhook URL

  1. Click “Add Webhook”
  2. Enter your endpoint URL (e.g., https://api.myapp.com/webhooks/miniback)
  3. Click “Test” to send a test event
  4. If successful, click “Add” to save

3. Configure Event Topics

By default, webhooks receive all event types. To customize:

  1. Click the expand arrow on your webhook
  2. Check/uncheck event topics:
    • New Feedback: Receive feedback.created events
    • Status Changed: Receive feedback.updated events
  3. 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

  1. Miniback generates a signature using your webhook secret and the request body
  2. The signature is sent in the X-Miniback-Signature header
  3. Your endpoint recalculates the signature and compares it
  4. 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}), 200

Next.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() or hmac.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:

  1. Go to Settings → Notifications
  2. Use the toggle switch next to each webhook
  3. Disabled webhooks are saved but won’t receive events

Remove Webhook

To permanently delete a webhook:

  1. Click the trash icon next to the webhook
  2. Confirm deletion
  3. 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:// (not http://)
  • Exception: http://localhost allowed 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

  1. Submit test feedback via your widget
  2. Check dashboard - does feedback appear?
    • If NO: Widget issue
    • If YES: Check webhook configuration
  3. 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: 456

Header Reference

HeaderDescription
Content-TypeAlways application/json
User-AgentAlways Miniback-Webhook/1.0
X-Miniback-SignatureHMAC-SHA256 signature for verification
X-Miniback-TimestampISO 8601 timestamp of the event

You can identify Miniback webhooks by checking for:

req.headers["user-agent"] === "Miniback-Webhook/1.0";

What’s Next?


Need help? See Troubleshooting or contact support through your dashboard.

Last updated on