CapableDocs

Search the API docs

Find an endpoint or guide

Get an API key

Get startedWebhooks

Get started

Webhooks

Capable sends a signed POST to your endpoint when something happens on the cap table, so your systems stay in step without polling.

Add an endpoint

In Capable, open Settings → Integrations → Add webhook. Enter an https:// URL and tick the events you want, or * for every event. Open Integrations

Capable then shows the endpoint’s signing secret, which starts with whsec_. It is shown once: store it with your other secrets. Use Test next to the endpoint to send it a webhook.test event.

Events

EventSent when
security.createdA security is recorded in the app, a SAFE or note is issued from a fundraise, or one is created through the API.
security.updatedA security is corrected or its terms are modified, funds are received on a SAFE, or drafts are published to the cap table.
security.exercisedAn exercise is recorded, or an exercise request is completed.
security.cancelledA security is cancelled.
stakeholder.createdA stakeholder is added in the app, from HRIS sync, or through the API.
stakeholder.terminatedA stakeholder's termination is recorded.
esign.completedEvery signer has signed a document sent for e-signature.
approval.approvedA signed board or stockholder consent is recorded or attached, including consents read from an imported data room.
valuation.recordedA 409A valuation is recorded.
import.committedAn imported cap table is saved to the company.
webhook.testYou choose Test next to the endpoint. It goes to that endpoint only, whatever events it subscribes to.

Payload

Each delivery is a JSON POST. data says what changed and who changed it; fetch the record from the API for its current state.

POST /hooks/capable
{
  "id": "evt_9w2k4n7q1x5c",
  "event": "security.exercised",
  "companyId": "org_northwind8k2",
  "createdAt": "2026-11-05T18:24:51.112Z",
  "data": {
    "entityType": "security",
    "entityId": "sec_1v9ha3opt004",
    "summary": "Exercised 2,000 of OPT-4 → CS-31",
    "actor": "Maya Chen",
    "meta": null
  }
}
FieldDescription
idUnique id of this delivery, starting with evt_. Use it to ignore a delivery you have already handled.
eventThe event name, also sent in the X-Capable-Event header.
companyIdThe company it happened in.
createdAtWhen Capable sent it, ISO 8601 in UTC.
data.entityTypeThe kind of record, such as security, stakeholder or valuation.
data.entityIdThe record's id, such as a security id you can pass to GET /securities/{id}.
data.summaryOne line saying what happened, as shown in the activity log.
data.actorWho did it: a person's name, API for an API key, or System.
data.metaExtra details for some events, such as the certificate id and status of a security created through the API. Often null.

Verify the signature

Every delivery has an X-Capable-Signature header: sha256= followed by the hex HMAC-SHA256 of the raw request body, keyed with the endpoint’s signing secret. Compute it yourself and compare in constant time before trusting the payload.

import crypto from "node:crypto";
import express from "express";

const app = express();

// Read the raw body: the signature covers the exact bytes Capable sent.
app.post("/hooks/capable", express.raw({ type: "application/json" }), (req, res) => {
  const expected = "sha256=" + crypto
    .createHmac("sha256", process.env.CAPABLE_WEBHOOK_SECRET)
    .update(req.body)
    .digest("hex");
  const given = req.get("X-Capable-Signature") ?? "";
  const ok = given.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected));
  if (!ok) return res.status(401).end();

  const event = JSON.parse(req.body.toString("utf8"));
  console.log(event.event, event.data.entityId);
  res.status(200).end();
});
Compute the HMAC over the body exactly as received. Parsing the JSON and serialising it again changes the bytes and the signature will not match.

Delivery

  • Capable sends each event once, right after the change, and waits up to 8 seconds for a response.
  • Any 2xx status counts as delivered. There are no automatic retries, so return 200 quickly and do slow work afterwards.
  • Integrations shows each endpoint’s last status, when it last received an event, and how many deliveries in a row have failed.
  • A successful delivery resets the failure count. After 20 failed deliveries in a row, the endpoint is turned off and shows as Disabled; delete it and add it again to turn it back on.
  • Deliveries are not guaranteed to arrive in order. Use createdAt, and read the record from the API when order matters.