Developer API & webhooks
WorkBOS exposes a REST API and outgoing webhooks so you can read and write your workspace data from your own code or no-code tools. Every request is scoped to your workspace by the API key, so you can never reach another workspace. Set both up under Developer.
- 1
Create an API key
Developer > API keys > Create. Choose what the key may do: Read only (the default) or Read and write. The full key (snrp_...) is shown once. Copy it immediately. Send it on every request as a bearer token: Authorization: Bearer snrp_... Keys are workspace-scoped. You can change a key between read-only and read-write later without reissuing it, and revoke any key instantly, from the same screen.
- 2
Read data
Base URL: https://dkjdtyzjdkumnpdyezbs.supabase.co/functions/v1/api-v1. GET /api-v1/<resource> lists records (use ?limit= up to 200 and ?offset= to page); GET /api-v1/<resource>/<id> returns a single record.
- 3
Write data
POST /api-v1/<resource> creates from a JSON body, PATCH /api-v1/<resource>/<id> updates, DELETE /api-v1/<resource>/<id> removes. Only a key with the write scope may do any of it: a read-only key is refused with 403 on every non-GET. Only the documented fields are accepted and your workspace is applied automatically. You never send an org id.
- 4
Stay inside the rate limits
600 requests per minute per key and 1,200 per minute per calling IP address, counted in a sliding 60-second window. Going over returns 429 rate_limited and the request is refused, not queued, so retry it after backing off. The per-IP ceiling is counted before the key is read, so a shared egress address (a cloud function, an office NAT) shares one bucket. Both numbers are shown on Developer > API keys.
| Resource | Access | Writable fields |
|---|---|---|
| tasks | Read + write | name, status, priority, project_id, assignee_id, due_date, description |
| projects | Read + write | name, status, company_id, pm_id, description |
| deals | Read + write | title, stage (Lead/Qualified/Proposal/Negotiation/Won/Lost), value, company_id, contact_id, owner_id, expected_close, notes |
| contacts | Read + write | full_name, email, phone, title, status, company_id, owner_id, notes |
| accounts | Read + write | CRM companies: name, industry, website, phone, owner_id, notes |
| companies | Read + write | Workspace companies (the Company layer): name, description |
| invoices | Read only | Financial records cannot be written through the API, whatever scope the key holds |
What “Read and write” actually covers: tasks, projects, deals, contacts, accounts and companies. Invoices are readable and never writable, for every key. Narrowing a key that something is already using takes effect immediately, so the console asks you to confirm and tells you when the key was last used before it does it.
company_id on deals and contacts points to an accounts record (a CRM company), not the workspace companies layer. Create or look up the account first, then pass its id.
Webhooks push events to your own URL the instant something happens, so you never have to poll. Add an endpoint under Developer > Webhooks, choose the events you care about (or All events), and each matching change sends a signed POST.
Every event WorkBOS can send is listed below. It is the same list WorkBOS itself holds, and a build check fails when the platform starts sending a type this table does not name, so it cannot quietly fall behind again: it listed eight events while nineteen were being sent.
| Event | Fires when | Can contain text from outside your workspace |
|---|---|---|
| task.created | A task is created | No |
| project.created | A project is created | No |
| client.created | A client is created | No |
| deal.created | A deal is created | No |
| deal.stage_changed | A deal moves to a different stage | No |
| deal.won | A deal is marked Won | No |
| invoice.created | An invoice is created | No |
| invoice.paid | An invoice is marked paid | No |
| tag.added | A tag is added to a record | No |
| lead.became_hot | A lead crosses the hot threshold | No |
| form.submitted | Someone submits a published form | Yes |
| survey.completed | Someone completes a survey | Yes |
| sms.received | An inbound text message arrives | Yes |
| voicemail.received | A voicemail is left, with its transcript | Yes |
| call.completed | A call ends | Yes |
| call.missed | A call is missed | Yes |
| call.ai_intake | The AI receptionist takes an enquiry | Yes |
| chief.flags | The assistant flags something for attention | No |
| chief.dream.completed | An overnight assistant run finishes | No |
| webhook.secret_rotated | An endpoint signing secret is rotated | No |
Choose your events rather than taking All events. “All events” means every event that exists now AND every one added later, so an endpoint set up for deals and invoices started receiving voicemail transcripts and inbound text messages the day those events shipped: words a member of the public typed or spoke, arriving in whatever channel you pointed at us. New endpoints now start with the eight business events above; the last column tells you which of the rest carry outside text before you add them.
Verify every delivery. Prefer X-WorkBOS-Signature-V1, which looks like t=1767225600,v1=<hex> : take the t value, check it is within five minutes of your own clock, then recompute HMAC-SHA256 over the exact string "<t>.<raw body>" using your endpoint secret and compare the v1 half. X-WorkBOS-Signature is still sent, byte for byte, and is "sha256=" followed by the HMAC of the body alone. That one verifies the same delivery for ever, which is why the timestamped one exists. An endpoint with no secret is never delivered to at all, so a signature header is never empty. Slack, Teams and Discord endpoints receive a pre-formatted message instead of raw JSON.
Verify against X-WorkBOS-Signature and X-WorkBOS-Event. Deliveries also carry a deprecated older pair with identical values, kept only so integrations written before the rename keep verifying; it stops being sent after the date named in the X-WorkBOS-Deprecation header on every delivery. Nothing else about the signature has changed, so moving across is a one-line edit.
- Deduplicate: every body now carries workbos_delivery_id and workbos_timestamp, in every format including Slack, Teams, Discord and automation webhooks, which used to carry neither. The same id is in the X-WorkBOS-Delivery header for convenience, but read it from the BODY: the body is signed and the header is not. A failed delivery is retried up to six times with backoff, so the same id can arrive more than once.
- Replay: check the timestamp. X-WorkBOS-Timestamp and the t= value inside X-WorkBOS-Signature-V1 are both covered by that signature, so a captured delivery stops verifying once it is older than your tolerance. Five minutes is a sensible one. The older body-only signature has no expiry at all, which is the reason to move across.
Reliable delivery: a server-side dispatcher POSTs each event and records the real HTTP status. Failed or timed-out deliveries retry automatically with exponential backoff (up to 6 attempts); return any 2xx to acknowledge. Delivery status + attempts are visible under Developer > Webhooks.