Inspector and Troubleshooting
Webhook Inspector
The customer Webhook Inspector exposes the delivery state recorded by MiniVoice. Customers can inspect:
- event type, event ID, and sequence
- delivery status and safe destination
- exact stored payload
- delivery attempt history and trigger type
- HTTP response status and sanitized response-body preview
- sanitized errors and response timing
- signature metadata
- next retry time and manual resend eligibility
Each attempt is labeled as an initial delivery, automatic retry, or manual resend. Response headers and bodies can be truncated for safe storage. Credentials, cookies, tokens, and secret-like headers are redacted. Admin-only queue-health controls are not part of the customer Inspector.
Historical payloads are displayed exactly as originally sent, including older body formats. See Historical payload compatibility.
Test a receiver
- Configure a test Application with a
webhook_urlandwebhook_secret. - Trigger a real call event.
- Verify the raw-body signature before parsing JSON.
- Confirm
X-MiniVoice-Event-IDmatches the bodyidandX-MiniVoice-Event-Typematchestype. - Persist
id, return2xx, and confirm the Inspector reportsdelivered. - For retry testing, temporarily return
500; confirm a new attempt appears with the same event ID and a different attempt ID.
Minimal Node.js receiver
This Express example requires raw JSON bodies so signature verification occurs before parsing.
import crypto from "node:crypto";
import express from "express";
const app = express();
const processedEventIds = new Set(); // Replace with durable storage.
app.post("/webhooks/minivoice", express.raw({ type: "application/json" }), (req, res) => {
const timestamp = req.get("X-MiniVoice-Timestamp");
const received = req.get("X-MiniVoice-Signature");
const secret = process.env.MINIVOICE_WEBHOOK_SECRET;
if (!timestamp || !received || !secret) return res.sendStatus(401);
const expected = "v1=" + crypto.createHmac("sha256", secret)
.update(timestamp).update(".").update(req.body).digest("hex");
const actualBytes = Buffer.from(received);
const expectedBytes = Buffer.from(expected);
if (actualBytes.length !== expectedBytes.length ||
!crypto.timingSafeEqual(actualBytes, expectedBytes)) {
return res.sendStatus(401);
}
const event = JSON.parse(req.body.toString("utf8"));
if (processedEventIds.has(event.id)) return res.sendStatus(204);
switch (event.type) {
case "call.completed":
// Queue your call-completion work.
break;
case "recording.completed":
// Queue your recording work.
break;
default:
// Ignore event types this integration does not use.
break;
}
processedEventIds.add(event.id);
return res.sendStatus(204);
});
app.listen(3000);
Use a database uniqueness constraint for event IDs in production; the in-memory Set is only a minimal example.
Troubleshooting
No webhook received
- Confirm the Application has the intended destination.
- Confirm the URL is valid and publicly reachable. HTTPS is required unless insecure HTTP was explicitly enabled for that Application.
- Check the Inspector status, latest response or sanitized error, and retry state.
- Trigger a known call event and confirm it belongs to the intended Application.
Duplicate event
Duplicate delivery is expected to be possible. Deduplicate using the top-level event id, return success for an event already processed, and do not repeat business side effects.
Events appear out of order
Use sequence, not HTTP arrival order, to reconstruct event-generation order for a call. Historical events may not have sequence populated.
Signature verification fails
- Verify against the exact raw request body.
- Use the correct Application webhook signing secret.
- Read the Unix timestamp from
X-MiniVoice-Timestamp. - Expect the received signature to start with
v1=. - Do not parse and reserialize JSON before calculating HMAC.
4xx response
Most 4xx responses are final. Only 408, 425, and 429 are automatically retried. Inspect the response and correct your receiver before manually resending an eligible failed event.
Repeated 5xx or timeout
Return success as soon as the event is durably accepted and process slow work asynchronously. Review each attempt in the Inspector to compare response status, duration, and sanitized errors.