Send webhooks to external services
Webhooks send a real-time HTTP POST with a JSON payload to a URL you control whenever selected events happen in Onplana. Use them to mirror task changes into another tool, kick off CI jobs, or feed a data warehouse.
Create a webhook
Section titled “Create a webhook”-
Open Apps and integrations in the sidebar and go to the Webhooks tab.
-
Select New Webhook.
-
Give it a Name, enter your Endpoint URL, and tick the Events to subscribe to. The URL must be a public http or https address; private and internal network addresses are rejected.
-
Select Create Webhook. Use the test action on the new webhook’s card to send a sample delivery and confirm your endpoint responds.
Supported events
Section titled “Supported events”You can subscribe to any combination of these events:
| Tasks | Projects | Clients and deals | Other |
|---|---|---|---|
task.created |
project.created |
company.created |
comment.created |
task.updated |
project.updated |
company.updated |
member.added |
task.deleted |
project.deleted |
deal.stage_changed |
member.removed |
task.status_changed |
project.billable_changed |
deal.quote_accepted |
migration.completed |
task.preview_ready |
project.schedule_pushed |
||
project.readiness_checked |
|||
project.closeout_signed_off |
|||
project.archived |
|||
project.unarchived |
Each delivery is a POST with body
{ "event": "...", "timestamp": "...", "data": { ... } }, and the event
name is repeated in the X-Webhook-Event request header.
Project completion events
Section titled “Project completion events”Four events follow a project through its finish. They fire the same way
whether the action happened in the app, through the API or through an AI
agent. Every data object carries projectId and projectName, plus:
| Event | Fires when | Extra data fields |
|---|---|---|
project.readiness_checked |
A readiness check is evaluated and recorded, before completing or archiving | checkId, transition (COMPLETE or ARCHIVE), mode (OFF, WARN or BLOCK), passed, requiredGapCount, requiredGaps (a list of { criterionId, title }), source, actorId |
project.closeout_signed_off |
Every designated reviewer has signed off the closeout | closeoutId, signedOffAt, reviewerCount, reviewerIds, actorId (who recorded the last sign-off) |
project.archived |
A completed or cancelled project is archived | status, archivedAt, readinessMode, actorId |
project.unarchived |
An archived project is unarchived | status, previouslyArchivedAt, actorId |
For example, a project.archived delivery looks like this:
{ "event": "project.archived", "timestamp": "2026-10-13T14:02:11.000Z", "data": { "projectId": "cmabc123", "projectName": "Website Redesign", "status": "COMPLETED", "archivedAt": "2026-10-13T14:02:10.000Z", "readinessMode": "WARN", "actorId": "cmuser456" }}Archiving and unarchiving never change the project’s status. The same four moments are also workflow triggers; see Build a workflow.
Schedule push event
Section titled “Schedule push event”project.schedule_pushed fires once when a plan edited in
Onplana Schedule is
sent back to its project, never once per task. It is its own event rather
than project.updated, because a push rewrites tasks, not the project’s own
fields. A push that changed nothing sends nothing.
| Field | Meaning |
|---|---|
projectId |
The project the plan was sent to |
actorId |
Who sent it |
created, updated, moved, deleted |
How many tasks the push created, changed, moved in time and removed |
linked, unlinked |
How many dependencies it added and removed |
rescheduled |
How many tasks the schedule engine moved as a result |
Client and deal events
Section titled “Client and deal events”These follow the clients and the pipeline in Onplana Services, so an accounting or CRM system can stay in step.
| Event | Fires when | data fields |
|---|---|---|
company.created |
A client is created | id, name, ownerId (the account owner, or null) |
company.updated |
A client is edited | id, name, ownerId |
deal.stage_changed |
A deal moves to another stage, from the board, the API or an AI agent | dealId, title, companyId, before ({ stage }), after ({ stage }) |
deal.quote_accepted |
A client accepts the deal’s quote from the shared quote link | dealId, title, companyId, stage, value (or null), currency, acceptedBy ({ fullName, email }), acceptedAt, snapshotHash |
Winning a deal is a deal.stage_changed with after.stage set to WON;
there is no separate win event. Accepting a quote never moves the stage on
its own, which is why it has an event of its own. See
Quote and win.
For example, a deal.stage_changed delivery looks like this:
{ "event": "deal.stage_changed", "timestamp": "2026-10-13T14:02:11.000Z", "data": { "dealId": "cmdeal789", "title": "Office fit-out, phase 2", "companyId": "cmco123", "before": { "stage": "PROPOSAL" }, "after": { "stage": "WON" } }}Verify the signature
Section titled “Verify the signature”Every delivery is signed so your receiver can prove it came from Onplana.
The X-Webhook-Signature header carries the HMAC-SHA256 of the raw
request body, computed with the webhook’s secret and hex-encoded. To
verify, compute the same HMAC over the exact bytes you received and
compare the two values with a constant-time comparison:
const crypto = require('crypto')const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex')const valid = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader))Each webhook gets its own secret, generated when it is created. The secret
is returned only in the API response that creates the webhook and is
redacted from every later read, so if your receiver needs to verify
signatures, create the webhook through the API (you can also supply your
own secret in that call) and store the secret with your other
credentials.
Delivery and failure handling
Section titled “Delivery and failure handling”- Your endpoint has 10 seconds to respond. Any 2xx status counts as a successful delivery.
- Every webhook card has a Recent Deliveries view showing the latest deliveries with event, HTTP status, and response time.
- Failures increment a per-webhook failure counter; a success resets it to zero. After 10 consecutive failures the webhook stops being delivered to, so a dead endpoint cannot generate endless retries.
- To revive a webhook that hit the failure limit, fix your endpoint and send a test delivery; a successful test resets the counter.
Best practices
Section titled “Best practices”- Subscribe to the smallest event set that meets your need. A webhook subscribing to every event firehose gets noisy; pick precise events.
- Always verify signatures in production. Skipping signature verification lets anyone POST to your endpoint. Use HMAC-SHA256 + constant- time comparison.
- Build idempotent receivers. Onplana doesn’t retry, but networks have hiccups. Receivers that handle duplicate events correctly avoid weird state.
- Monitor delivery health. Recent Deliveries shows pass/fail rates. A webhook climbing toward the 10-failure cap needs intervention before it dies.
- Document the contract internally. Which events fire what shape, who receives them, what they trigger. New team members can reverse-engineer it, but documentation is faster.
Troubleshooting / common pitfalls
Section titled “Troubleshooting / common pitfalls”- Test delivery returned 404. Endpoint
URL wrong. Verify the path in the URL
- that the server is up.
- Test delivered but signature didn’t match. Re-check secret storage; you may be hashing the URL-encoded body instead of raw bytes.
- All deliveries failing. Endpoint outage. Fix it; send test delivery to reset failure counter.
- Webhook stopped firing. Hit 10- failure cap. Fix endpoint + test delivery.
- Private network URL rejected. Onplana validates URLs and blocks RFC1918 private addresses. Use a publicly addressable proxy (ngrok, Cloudflare Tunnel) for testing internal endpoints.
How this combines with other features
Section titled “How this combines with other features”- Webhooks + Workflows. Workflows can call webhooks as a step. See Build a workflow.
- Webhooks + Audit log. Webhook create/update/delete events go to the audit log. See Read audit logs.
- Webhooks + Integrations. Onplana webhooks are the outbound integration primitive. Combine with inbound receivers (Slack incoming-webhook, CI, etc.).
- Webhooks + API tokens. API tokens are the inbound complement; together they cover bidirectional integration.
Coming from another tool
Section titled “Coming from another tool”| Tool | Mapping |
|---|---|
| GitHub webhooks | Direct concept; HMAC-SHA256 signed |
| Stripe webhooks | Direct |
| Linear webhooks | Direct |
| Slack outgoing webhooks | Direct |
| ServiceNow REST messages | Direct |
Who can manage webhooks? By default, Owners and Admins. This is the organization’s webhook management permission, so you can extend it to Portfolio Managers from the permissions matrix in Organization Settings.
Are failed deliveries retried automatically? No. A delivery is attempted once per event. Build idempotent receivers and use the Recent Deliveries view to spot gaps; the failure counter and test delivery cover recovery.
Can I pause a webhook without deleting it? Yes. Toggle it inactive from its card; the configuration and delivery history are kept, and no events are sent until you re-enable it.
Can I customize the payload shape? No, the payload shape is fixed per event for consistency. To reshape, use a middleware (Zapier, n8n) between Onplana and your endpoint.
Is there an event firehose webhook (all events)? No, you subscribe to specific events. Subscribe to every event if you want firehose behavior.
Can I see what payload was sent? Yes, Recent Deliveries shows the request body. Useful for debugging.
Related
Section titled “Related”- Build a workflow, workflows can call webhooks as a step
- Create API tokens, inbound integration
- Read audit logs, webhook events are audited
Was this helpful?
Thanks for your feedback!