Skip to main content

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

  1. Open Account Management → Integrations.
  2. Choose Generic or Zapier. Zapier is inferred for Zapier hostnames; the HTTP contract remains an outbound webhook.
  3. Enter the endpoint URL.
  4. Optionally add a signing secret.
  5. Enable handoff and save.
  6. Select Send test webhook.
  7. Confirm the receiver returns a 2xx status 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:

HeaderMeaning
Content-Typeapplication/json
X-Sol-Eventsolhelps.webhook.test or solhelps.insight.handoff
X-Sol-DeliveryUnique delivery UUID
X-Sol-Providergeneric or zapier
X-Sol-TimestampMillisecond timestamp used for signing
X-Sol-SignatureHMAC-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, and sentAt;
  • 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: missing insights.export or assistant-scope access.
  • 409: integration disabled or no URL saved.
  • 502 with endpoint status: receiver returned non-2xx.
  • 502 timeout: 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.