Core concepts · roadmap

Webhooks

Push events from Worksible into your systems instead of polling. Webhooks let you react to invoice payments, contract signatures, timesheet approvals, KYB outcomes and more, with signed payloads and at-least-once delivery.

Roadmap: 2026 Q3
Webhooks are scheduled for the 2026 Q3 release. The shape below is the design we're committed to; the dates are firm. Until then, use polling on the read endpoints (a 5-minute cron over /v1/companies/me/invoices?status=paid is what most customers do today). Subscribe to the changelog to be notified.
Available on
GlobalEnterpriseAOR

Webhooks will be available on Global and above. Professional customers can keep using polling; we don't plan to gate read endpoints behind a higher tier.

Planned events

Each event maps to a specific business transition. Names use resource.action in lowercase.

EventWhen
invoice.issuedAn invoice transitioned from draft to issued.
invoice.paidPayment was received against an invoice.
invoice.overdueAn issued invoice passed its due date without payment.
payment.succeededAn inbound payment cleared.
payment.failedAn inbound payment was declined or returned.
payout.sentA payout to a freelancer left our account.
payout.deliveredThe payout reached the recipient bank.
payout.failedA payout was rejected by the rail.
contract.signedAll parties signed a contract.
contract.declinedA signer declined or expired the contract.
timesheet.submittedA freelancer submitted a timesheet for approval.
timesheet.approvedAn approver approved a timesheet.
kyb.approvedAn organization passed KYB review.
kyb.rejectedAn organization failed KYB review.

Payload shape

Every event uses the same envelope. The data field is a snapshot of the resource at the time of the event — subsequent state changes produce new events; we never silently update a previously delivered event.

json
{
  "id": "evt_2026q3_8f3a",
  "type": "invoice.paid",
  "createdAt": "2026-08-12T10:14:22.000Z",
  "organizationId": "org_2024_AbCd",
  "data": {
    "id": "inv_456",
    "invoiceNumber": "WK-202608-019",
    "totalCents": 151250,
    "currency": "EUR",
    "status": "paid",
    "paidAt": "2026-08-12T10:14:21.000Z"
  }
}

Signing

Each request carries an X-Worksible-Signature header containing a HMAC-SHA256 of {timestamp}.{rawBody} signed with the endpoint's secret. Compute the same on your side and reject any request whose signatures don't match.

text
X-Worksible-Signature: t=1755013241,v1=4f3a0c1b2d...
X-Worksible-Event-Id: evt_2026q3_8f3a
Content-Type: application/json
node.js · verify
import { createHmac, timingSafeEqual } from "node:crypto";

export function verifySignature(rawBody, signatureHeader, secret) {
  const parts = Object.fromEntries(
    signatureHeader.split(",").map((s) => s.split("=")),
  );
  const expected = createHmac("sha256", secret)
    .update(`${parts.t}.${rawBody}`)
    .digest("hex");
  // Reject anything older than 5 minutes (replay protection).
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
  return timingSafeEqual(
    Buffer.from(parts.v1, "hex"),
    Buffer.from(expected, "hex"),
  );
}

Delivery semantics

  • At-least-once.If your endpoint doesn't answer with a 2xx within 15 seconds, we retry with exponential backoff (1m, 5m, 30m, 2h, 12h, 24h). After 24 hours of failure we mark the endpoint disabled and email the workspace owner.
  • Out of order.Don't assume events arrive in the order they happened. Each event carries createdAt; if you store last-seen, ignore older events.
  • Deduplicate by event id. idis unique even across retries. If you've seen an id, the safe move is to ack with 200 and skip processing.

Polling pattern (today)

Until webhooks ship, the canonical pattern is a 5-minute cron that reads each list endpoint you care about with a createdAt[gte] filter set to your last successful sync. Worksible-grade integrations work fine on polling; we run a few of them ourselves.

bash
# Look back 10 minutes (last-seen 5min, plus 5min jitter).
LAST_SEEN=$(date -u -d "-10 minutes" +"%Y-%m-%dT%H:%M:%SZ")
curl "https://api.worksible.com/v1/companies/me/invoices?status=paid&paidAt[gte]=${LAST_SEEN}" \
  -H "X-API-Key: $WORKSIBLE_API_KEY"