Configure an Insights Webhook Handoff
Webhook handoff sends a selected Insights action to an HTTPS endpoint you control. It is a configurable outbound webhook, not a native Jira, Linear, GitHub, Slack, CRM, or support-platform integration.
Applicability
The feature is shown on Growth and Business. Reading, changing, testing, and
sending require insights.export; assistant scope also applies when sending a
specific insight. Production endpoints must use HTTPS. Development accepts
plain HTTP only for localhost.
Configure and test
- Open Account Management → Integrations.
- Choose Generic or Zapier. Zapier is inferred for Zapier hostnames; the HTTP contract remains an outbound webhook.
- Enter the endpoint URL.
- Optionally add a signing secret.
- Enable handoff and save.
- Select Send test webhook.
- Confirm the receiver returns a
2xxstatus within 10 seconds.
A failed request is reported to the dashboard. Sol Helps does not implement an automatic retry queue in the inspected handoff route; retry manually after fixing the receiver.
HTTP request
Sol Helps sends POST with JSON and these headers:
| Header | Meaning |
|---|---|
Content-Type | application/json |
X-Sol-Event | solhelps.webhook.test or solhelps.insight.handoff |
X-Sol-Delivery | Unique delivery UUID |
X-Sol-Provider | generic or zapier |
X-Sol-Timestamp | Millisecond timestamp used for signing |
X-Sol-Signature | HMAC-SHA256 hex digest, only when a secret is configured |
The signature input is:
<X-Sol-Timestamp>.<raw request body>
Verify against the raw bytes before parsing JSON. Also reject stale timestamps according to your receiver's replay policy.
Safe Node receiver example
import crypto from "node:crypto";
import express from "express";
const app = express();
app.post("/sol-helps", express.raw({type: "application/json"}), (req, res) => {
const timestamp = String(req.get("X-Sol-Timestamp") || "");
const supplied = String(req.get("X-Sol-Signature") || "");
const expected = crypto
.createHmac("sha256", process.env.SOL_HELPS_WEBHOOK_SECRET)
.update(`${timestamp}.${req.body.toString("utf8")}`)
.digest("hex");
const valid =
supplied.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(supplied), Buffer.from(expected));
if (!valid) return res.status(401).send("invalid signature");
const event = JSON.parse(req.body.toString("utf8"));
// Enqueue idempotently using event.deliveryId; acknowledge quickly.
return res.status(204).end();
});
Keep the signing secret in server-side secret storage. Do not put it in a browser bundle or repository.
Insight payload
solhelps.insight.handoff includes:
event,deliveryId, andsentAt;- workspace ID, name, and plan tier;
- actor user ID, email, and role;
- assistant, date-range, and source filter scope;
- theme label and bounded metrics;
- recommendation fields;
- up to six evidence snippets, each truncated to 300 characters; and
- an optional deep link to the insight.
The test payload contains workspace and actor fields plus a connection message. Evidence and actor email can contain sensitive or personal information. Protect the endpoint, restrict downstream access, and apply an appropriate retention policy.
Send and verify a handoff
From Friction Insights, select a theme and send its recommended action.
Record the delivery ID at the receiver and confirm a 2xx result in Sol Helps.
Use delivery ID for idempotency because a user can manually send again.
Disable or troubleshoot
Turn off Enabled and save to prevent real handoffs. Clearing the signing secret is a separate explicit action.
400: invalid URL, JSON, or missing required theme fields.403: missinginsights.exportor assistant-scope access.409: integration disabled or no URL saved.502with endpoint status: receiver returned non-2xx.502timeout: receiver did not finish within 10 seconds.
Collect the delivery ID, event type, timestamp, status, and a redacted response preview. Never place the secret or full evidence payload in a support ticket.