Docs

Send events from your workspace

Pinnakle OS can tell another app the moment something happens in your workspace: a client is added, a cut moves to QC, a post goes live, a lead arrives, a promise runs late. Point it at Zapier, Make, n8n or a small server of your own. Owners and admins set it up under Settings → Integrations → Send events.

Set it up

  1. Create a webhook in your tool (in Zapier: a “Catch Hook” trigger) and copy its address. It must be a public https address.
  2. In Pinnakle OS open Settings → Integrations → Send events → Add endpoint, paste the address and tick the events you want.
  3. Copy the signing secret when it appears. It is shown once. Zapier and Make do not need it; your own server uses it to check the signature.
  4. Press Send test event. A test.ping event arrives and the answer appears in the endpoint's log.

Prefer a message in Slack? Add an endpoint of the kind Slack message on events instead: the same events arrive as one short sentence each, in the channel your Slack webhook points at.

What we send

One POST per event, with a JSON body and these headers:

  • X-Pinnakle-Event: the event name, e.g. lead.created
  • X-Pinnakle-Delivery: a unique id for this delivery (the same id on every retry)
  • X-Pinnakle-Timestamp: Unix seconds when this try was sent (fresh on every retry)
  • X-Pinnakle-Signature: v1= and a hex HMAC-SHA256, see below

Every body has the same envelope. data is the part that differs per event (below). Payloads carry names and ids; they never carry money (retainers, invoices, deal values) or anything meant for the team only.

Envelope
{
  "id": "evt_8f3k2m1p9q0r",        // one id per event; the same on every endpoint and every retry
  "type": "lead.created",
  "createdAt": "2026-10-20T09:30:00.000Z",
  "workspace": { "id": "…", "name": "Your Agency" },
  "data": { … }
}

Answer with any 2xx within 8 seconds. Redirects are not followed. Do the slow work after you have answered.

Check the signature

The signature is an HMAC-SHA256 of <timestamp>.<raw body> keyed with your endpoint's secret. Recompute it over the exact bytes you received, compare in constant time, and reject a timestamp more than 5 minutes old so an old request cannot be replayed.

Node.js
import { createHmac, timingSafeEqual } from "node:crypto";

// rawBody: the request body exactly as received (a string or Buffer), NOT re-serialised JSON.
export function isFromPinnakle(rawBody, headers, secret) {
  const ts = headers["x-pinnakle-timestamp"];
  const sig = headers["x-pinnakle-signature"] ?? "";
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; // too old: possible replay
  const expected = "v1=" + createHmac("sha256", secret).update(ts + "." + rawBody).digest("hex");
  return expected.length === sig.length && timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
}
Python
import hmac, hashlib, time

def is_from_pinnakle(raw_body: bytes, headers: dict, secret: str) -> bool:
    ts = headers.get("x-pinnakle-timestamp", "")
    sig = headers.get("x-pinnakle-signature", "")
    if not ts.isdigit() or abs(time.time() - int(ts)) > 300:
        return False  # too old: possible replay
    expected = "v1=" + hmac.new(secret.encode(), ts.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig)

Lost the secret? Press New secret on the endpoint; the old one stops working at once.

Retries, the log and switching off

  • A delivery that fails (no answer, a non-2xx answer, a redirect) is tried again up to three more times, 5 minutes, 30 minutes and 2 hours after the try before it. Retries go out with the hourly run, so a retry can arrive up to an hour later than its schedule.
  • Each endpoint keeps a log of its last 50 deliveries with the answer it got.
  • After 20 failed tries in a row the endpoint is switched off and the owners and admins get a notification. Fix the address, then switch it on again.
  • Events can arrive more than once and are not guaranteed to arrive in order. Use id to ignore a repeat.
  • Only public https addresses are accepted. Addresses on private or local networks are refused.

Events

Pick any of these per endpoint. test.ping is sent only by “Send test event”.

client.created Client added

A client is added, by hand or by the assistant.

{
  "id": "evt_8f3k2m1p9q0r",
  "type": "client.created",
  "createdAt": "2026-10-20T09:30:00.000Z",
  "workspace": {
    "id": "ckx1workspace01",
    "name": "Your Agency"
  },
  "data": {
    "client": {
      "id": "ckx1client0001",
      "name": "Acme Coffee Roasters",
      "short": "Acme",
      "status": "ONBOARDING",
      "niche": "Food and drink"
    }
  }
}

client.updated Client changed

A client's details or status change.

{
  "id": "evt_8f3k2m1p9q0r",
  "type": "client.updated",
  "createdAt": "2026-10-20T09:30:00.000Z",
  "workspace": {
    "id": "ckx1workspace01",
    "name": "Your Agency"
  },
  "data": {
    "client": {
      "id": "ckx1client0001",
      "name": "Acme Coffee Roasters",
      "short": "Acme",
      "status": "ACTIVE"
    },
    "changed": [
      "status",
      "niche"
    ]
  }
}

cut.status_changed Cut moved to a new stage

A cut moves between Raw in, Editing, QC, Client review, Approved, Posted or Cancelled.

{
  "id": "evt_8f3k2m1p9q0r",
  "type": "cut.status_changed",
  "createdAt": "2026-10-20T09:30:00.000Z",
  "workspace": {
    "id": "ckx1workspace01",
    "name": "Your Agency"
  },
  "data": {
    "cut": {
      "id": "ckx1cut000001",
      "title": "Launch reel #3",
      "type": "SHORT",
      "platform": "INSTAGRAM",
      "dueDate": "2026-11-05"
    },
    "client": {
      "id": "ckx1client0001",
      "name": "Acme Coffee Roasters"
    },
    "from": "EDITING",
    "to": "QC"
  }
}

post.scheduled Post scheduled

A calendar post is set to Scheduled.

{
  "id": "evt_8f3k2m1p9q0r",
  "type": "post.scheduled",
  "createdAt": "2026-10-20T09:30:00.000Z",
  "workspace": {
    "id": "ckx1workspace01",
    "name": "Your Agency"
  },
  "data": {
    "post": {
      "id": "ckx1post00001",
      "title": "Launch reel",
      "platform": "INSTAGRAM",
      "date": "2026-11-05",
      "status": "SCHEDULED",
      "url": null
    },
    "client": {
      "id": "ckx1client0001",
      "name": "Acme Coffee Roasters"
    }
  }
}

post.published Post published

A calendar post is marked Posted.

{
  "id": "evt_8f3k2m1p9q0r",
  "type": "post.published",
  "createdAt": "2026-10-20T09:30:00.000Z",
  "workspace": {
    "id": "ckx1workspace01",
    "name": "Your Agency"
  },
  "data": {
    "post": {
      "id": "ckx1post00001",
      "title": "Launch reel",
      "platform": "INSTAGRAM",
      "date": "2026-11-05",
      "status": "POSTED",
      "url": "https://www.instagram.com/p/abc/"
    },
    "client": {
      "id": "ckx1client0001",
      "name": "Acme Coffee Roasters"
    }
  }
}

update.sent Daily update sent

A client's daily update is sent to them.

{
  "id": "evt_8f3k2m1p9q0r",
  "type": "update.sent",
  "createdAt": "2026-10-20T09:30:00.000Z",
  "workspace": {
    "id": "ckx1workspace01",
    "name": "Your Agency"
  },
  "data": {
    "update": {
      "id": "ckx1upd0000001",
      "date": "2026-10-20",
      "channel": "WHATSAPP",
      "text": "Hi Priya, today we delivered 2 shorts…"
    },
    "client": {
      "id": "ckx1client0001",
      "name": "Acme Coffee Roasters"
    }
  }
}

lead.created Lead arrives

A new lead is added, by hand, by a connected tool or by your form.

{
  "id": "evt_8f3k2m1p9q0r",
  "type": "lead.created",
  "createdAt": "2026-10-20T09:30:00.000Z",
  "workspace": {
    "id": "ckx1workspace01",
    "name": "Your Agency"
  },
  "data": {
    "lead": {
      "id": "ckx1lead00001",
      "name": "Rahul Verma",
      "company": "Verma Fitness",
      "email": "rahul@verma.example",
      "phone": null,
      "source": "Website form",
      "niche": "Fitness",
      "status": "NEW"
    }
  }
}

lead.replied Lead replies

A lead replies, shows interest or books a call in a connected outreach tool.

{
  "id": "evt_8f3k2m1p9q0r",
  "type": "lead.replied",
  "createdAt": "2026-10-20T09:30:00.000Z",
  "workspace": {
    "id": "ckx1workspace01",
    "name": "Your Agency"
  },
  "data": {
    "lead": {
      "id": "ckx1lead00001",
      "name": "Rahul Verma",
      "company": "Verma Fitness",
      "email": "rahul@verma.example",
      "source": "Outreach"
    },
    "kind": "replied"
  }
}

promise.created Promise to a client made

Someone writes down a promise made to a client. Team-only promises are not sent.

{
  "id": "evt_8f3k2m1p9q0r",
  "type": "promise.created",
  "createdAt": "2026-10-20T09:30:00.000Z",
  "workspace": {
    "id": "ckx1workspace01",
    "name": "Your Agency"
  },
  "data": {
    "promise": {
      "id": "ckx1prom00001",
      "text": "Send the revised thumbnails",
      "dueOn": "2026-10-23",
      "status": "OPEN"
    },
    "client": {
      "id": "ckx1client0001",
      "name": "Acme Coffee Roasters"
    },
    "owner": {
      "id": "ckx1user00001",
      "name": "Darshan"
    }
  }
}

promise.late Promise is late

A promise to a client passes its due date without being kept.

{
  "id": "evt_8f3k2m1p9q0r",
  "type": "promise.late",
  "createdAt": "2026-10-20T09:30:00.000Z",
  "workspace": {
    "id": "ckx1workspace01",
    "name": "Your Agency"
  },
  "data": {
    "promise": {
      "id": "ckx1prom00001",
      "text": "Send the revised thumbnails",
      "dueOn": "2026-10-23",
      "status": "OPEN"
    },
    "client": {
      "id": "ckx1client0001",
      "name": "Acme Coffee Roasters"
    },
    "owner": {
      "id": "ckx1user00001",
      "name": "Darshan"
    },
    "daysLate": 1
  }
}

form.submitted Form answered

Someone submits one of your forms.

{
  "id": "evt_8f3k2m1p9q0r",
  "type": "form.submitted",
  "createdAt": "2026-10-20T09:30:00.000Z",
  "workspace": {
    "id": "ckx1workspace01",
    "name": "Your Agency"
  },
  "data": {
    "form": {
      "id": "ckx1form00001",
      "title": "Client onboarding",
      "kind": "ONBOARDING"
    },
    "response": {
      "id": "ckx1resp00001",
      "respondentName": "Priya Shah",
      "respondentEmail": "priya@acme.example",
      "answers": [
        {
          "label": "Brand colours",
          "value": "Teal and cream"
        }
      ]
    },
    "client": {
      "id": "ckx1client0001",
      "name": "Acme Coffee Roasters"
    }
  }
}

Questions? Sign in and use Report an issue, or write to pinnaklemedia@gmail.com. See also What's new.