Skip to content

Destinations: send themes to Linear, GitHub, Slack and more

Last updated October 25, 2026

A destination sends themes from Camelus to another app: Vicunia, an n8n, Zapier or Make workflow (which can open a Linear, GitHub or Jira issue), a Slack or Discord channel, or your own endpoint. They're all the same thing underneath: a signed HTTPS webhook. Presets only fill in the form.

Add one

Settings → Destinations → Add destination, then pick a preset:

Preset Sends Reports back
Vicunia The theme as a Vicunia request Build progress and "shipped", through Vicunia's source callback
n8n, Zapier, Make The canonical JSON Your workflow calls the status callback
Slack, Discord A short message to an incoming-webhook URL No
Webhook The canonical JSON, to any HTTPS endpoint Your code calls the status callback

You choose:

  • Products: all, or some.
  • When to send: by hand with Send to… on a theme, automatically on events (Pro), or both.
  • Filters for automatic theme events: a minimum score, a minimum number of requesters, types, paying requesters only.
  • Auth header: an extra header such as Authorization: Bearer …, stored encrypted.

The signing secret (whsec_…) is shown once when you create the destination. Rotate secret makes a new one; for 24 hours requests are signed with both, so you can switch your receiver over without missing anything.

Use Test to send a test.ping event. The delivery log shows every request with its status code, timing, body and the first 2 KB of the response, with Retry and Replay.

Events

Event When
theme.sent Someone clicks Send to… on a theme
theme.created A theme gets its second item (or an urgent bug)
theme.rising A theme's trend turns to rising
theme.threshold_reached A theme passes the destination's filters (once per theme)
theme.status_changed A theme's status changes, in Camelus or through a callback
theme.merged Two themes are merged (the payload is the surviving theme)
feedback.received A feedback item is processed
feedback.urgent An item is classified as high urgency
digest.ready The daily or weekly digest is computed
test.ping The Test button

Payload

POST https://your-endpoint.example.com/camelus
Content-Type: application/json
User-Agent: Camelus-Webhooks/1
Camelus-Event: theme.sent
Camelus-Delivery: dlv_6f1c…
Camelus-Timestamp: 1792915200
Camelus-Signature: v1=5c2a…
Camelus-Version: 2026-10-01
{
  "id": "evt_9b2e…",
  "type": "theme.sent",
  "created_at": "2026-10-25T08:00:00.000Z",
  "workspace": { "id": "k17…" },
  "product": { "id": "k57…", "slug": "habits", "name": "Habits", "domain": "habits.app" },
  "data": {
    "theme": {
      "id": "k97d…",
      "title": "CSV export ignores date locale",
      "summary": "Exports use US dates whatever the user's locale.",
      "type": "bug",
      "status": "open",
      "score": 4.2,
      "trend": "rising",
      "requesters": 17,
      "paying_requesters": 6,
      "mrr_affected_cents": null,
      "item_count": 21,
      "sentiment": { "negative": 82, "neutral": 18, "positive": 0 },
      "last_item_at": "2026-10-25T07:41:00.000Z",
      "url": "https://camelus.si/app/themes/k97d…",
      "representative_items": [
        { "id": "k3a…", "summary_en": "CSV export uses US dates for a French user.", "source": "widget", "received_at": "2026-10-25T07:41:00.000Z" }
      ]
    }
  },
  "callback": { "url": "https://api.camelus.si/v1/callbacks/k97d…", "token": "cbt_…" }
}

Payloads carry AI summaries and counts, never original text, names or emails. A workspace owner can turn on Include requester emails for one destination (for your own CRM, say); that choice is logged. mrr_affected_cents is null until a billing connector is set up: it's never estimated. Every schema is in the OpenAPI document, under webhooks.

Verify the signature

The signature is HMAC-SHA256, keyed with the whole signing secret, over {timestamp}.{raw body}. During a rotation the header holds two signatures, v1=…,v1=…; accept either.

import crypto from "node:crypto";

export function verifyCamelus(rawBody, headers, secret) {
  const ts = headers["camelus-timestamp"];
  if (!ts || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; // older than 5 minutes: a replay
  const expected = crypto.createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
  return (headers["camelus-signature"] ?? "").split(",").some((part) => {
    const sig = part.trim().replace(/^v1=/, "");
    return sig.length === expected.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
  });
}

Use the raw body exactly as received, before any JSON parsing.

Delivery

  • Answer with any 2xx within 10 seconds. Redirects are followed (at most 3).
  • Anything else is retried after about 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 12 hours: 7 attempts over a day. 410 Gone turns the destination off right away.
  • After 20 failed deliveries in a row, or 3 days of failures, the destination is paused and the owner gets an email. Resume it once the endpoint works.
  • Order isn't guaranteed. Use created_at to order events, and Camelus-Delivery (the same on every retry) to ignore repeats.
  • URLs must be public https:// addresses. Local and private networks are refused, also after DNS resolution and after redirects.

n8n

Two ready-made workflows: download, then Import from file in n8n.

  • Camelus → Linear: a sent theme becomes a Linear issue; the callback reports it as planned, and as shipped when the issue is completed.
  • Camelus → GitHub Issues: the same with a GitHub issue, shipped when it's closed.

After importing: paste your signing secret into the Verify node, pick your Linear team or GitHub repository and credentials, then copy the workflow's production webhook URL into a Camelus destination (preset n8n). The callback URL and token travel in a footer of the issue, so closing it reports back without a database.

Zapier

  1. Create a Zap with Webhooks by Zapier → Catch Raw Hook, and paste its URL into a destination (preset Zapier).
  2. Add a Code by Zapier step to check Camelus-Signature with the snippet above (the raw body and headers are in the trigger's output).
  3. Add Linear → Create Issue or GitHub → Create Issue, with the theme's title, summary and url. Put callback.url and callback.token in the description.
  4. In a second Zap triggered when the issue is completed or closed, add Webhooks by Zapier → POST to the callback URL from the description, with the header Authorization: Bearer <token> and the body {"status": "shipped", "external_id": "<issue id>", "external_url": "<issue url>"}.

Make

  1. Start a scenario with Webhooks → Custom webhook and paste its address into a destination (preset Make). Turn on Get request headers and JSON pass-through so the raw body is available for the signature.
  2. Add Linear → Create an issue or GitHub → Create an issue, mapping data.theme.title, data.theme.summary and data.theme.url, plus the callback block.
  3. In a second scenario watching for completed or closed issues, add HTTP → Make a request: POST to the callback URL, header Authorization: Bearer <token>, JSON body with status: shipped.

Slack and Discord

Create an incoming webhook in Slack (Apps → Incoming Webhooks) or Discord (Channel settings → Integrations → Webhooks) and paste its URL. By default they get new themes, rising themes, urgent feedback and the digest. Mentions in summaries never ping anyone.