Skip to main content
Salfio Docs

Search documentation

Type to search this documentation.

On this pageOverview

Webhooks

Have Salfio POST new activity events to your endpoint or fire a Claude Code remote routine.

Salfio can forward new activity events to a destination of your choice the moment they are recorded — no polling required. Two transports are supported:

  • URL endpoint — a plain POST to any HTTPS URL, signed with HMAC-SHA256 so you can verify it came from Salfio.
  • Claude routine — a POST with a Bearer token suitable for triggering a Claude Code remote routine.

Both share the same payload shape, the same delivery queue, and the same retry behaviour. They differ only in the auth header.

Two events are emitted in v1:

Event When it fires
activity.email.created A new email activity is captured from a connected Gmail integration.
activity.meeting.created A new meeting activity is captured by the Salfio meeting bot or Fireflies.

Manual notes (Conversation.type = "note") and other resource events are intentionally out of scope for v1.

  1. Open Settings → Webhooks in the dashboard.
  2. Click Add webhook and fill in:
    • Name — a human-friendly label.
    • Event — pick the event you want to react to.
    • TransportURL endpoint or Claude routine.
    • URL — must use https://. For Claude routines, paste the routine fire URL.
    • Token (Claude routines only) — the long-lived OAT token to send as Authorization: Bearer ….
  3. Save.

If you picked the URL-endpoint transport, the dashboard shows your signing secret exactly once. Copy it into a secret manager before closing the dialog — Salfio stores only an AES-256-GCM encryption of the secret and can't show it to you again.

For the URL endpoint transport every delivery body is the same JSON envelope. The activity field carries the same DTO that the Public API v1 returns when you fetch a single activity, so docs samples and production payloads cannot drift — with one exception: content is capped at 10 KiB in deliveries (a [content truncated at 10 KiB] marker is appended when the cut applies). When you need the full text of a long activity — a video-call transcript, say — fetch it by ID via GET /v1/clients/{clientId}/activities/{activityId}, which always returns the complete content.

{
  "event": "activity.email.created",
  "occurred_at": "2026-05-12T11:42:13Z",
  "organization": { "id": "9c1a…" },
  "activity": {
    "id": "8df8…",
    "clientId": "ad44…",
    "type": "email",
    "source": "gmail",
    "subject": "Quarterly review",
    "content": "Hi team — sharing the deck…",
    "occurredAt": "2026-05-12T11:42:13Z",
    "participants": ["[email protected]", "[email protected]"],
    "immutable": true,
    "archivedAt": null,
    "createdAt": "2026-05-12T11:42:14Z",
    "updatedAt": "2026-05-12T11:42:14Z"
  }
}

For the Claude routine transport the same envelope is delivered as a stringified JSON inside the text field, because Anthropic's /fire endpoint only accepts {"text": "..."}:

{ "text": "{\"event\":\"activity.email.created\",\"occurred_at\":\"…\",\"organization\":{\"id\":\"9c1a…\"},\"activity\":{…}}" }

If your routine wants the structured form, JSON.parse(text) inside the routine prompt gives you the same shape as the URL-endpoint body above.

Header Value
Content-Type application/json
X-Salfio-Event The event type, e.g. activity.email.created.
X-Salfio-Delivery A per-attempt UUID. Stable across the retry chain of one event — use it as an idempotency key on your side.
X-Salfio-Signature sha256=<hex> where <hex> is hmac_sha256(raw_body, signing_secret).
Header Value
Content-Type application/json
Authorization Bearer <your-token> (decrypted server-side per attempt).
anthropic-version 2023-06-01
anthropic-beta experimental-cc-routine-2026-04-01

Compute hmac_sha256(raw_body, signing_secret) and compare against the X-Salfio-Signature header (after stripping the sha256= prefix). Always use a constant-time comparison.

import crypto from "node:crypto";

export function verifySalfioSignature(rawBody, signature, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");
  const provided = signature.replace(/^sha256=/, "");
  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(provided, "hex");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
import hmac, hashlib

def verify_salfio_signature(raw_body: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    provided = signature.removeprefix("sha256=")
    return hmac.compare_digest(expected, provided)

Important: verify the signature against the raw request body bytes, before any JSON re-serialization on your side.

Salfio retries non-2xx responses (and any transport error) with exponential back-off plus jitter, up to 5 attempts total:

Attempt Delay before this attempt
1 immediate
2 ~30 s
3 ~2 min
4 ~10 min
5 ~30 min

After attempt 5 fails the delivery is marked terminally failed and the last error and status code are stored on the row (visible as "Last delivery" in the dashboard). Each attempt is bounded by a 10 s network timeout — endpoints that habitually take longer to respond will exhaust the retry budget.

Successful deliveries (any 2xx) are terminal — Salfio does not acknowledge response bodies, so don't rely on them carrying meaning back to us.

Each row in Settings → Webhooks has a Send test action that enqueues a synthetic delivery. The payload uses sample data so you can verify your endpoint without waiting for a real activity import. The test delivery goes through the same queue, signing path, retry curve, and "Last delivery" column as a real one — the only difference is the sample body.

  • Disable a webhook to pause future deliveries. Pending retries already in flight will still attempt to send.
  • Re-enable to resume.
  • Delete to remove the subscription. Pending and historical delivery rows are removed in the same transaction (ON DELETE CASCADE), so retries stop immediately.
Export
Suggest an edit to this page

Documentation menu