Building Custom Integrations With Onplana Webhooks
Onplana webhooks deliver HMAC-SHA256 signed event payloads to any HTTPS endpoint with automatic retry. Guide to events, signatures, and integration patterns.
Here's a quick way to tell whether a PM tool's webhook support is real or a press-release feature: ask for the event catalog. Not the marketing page. The actual list of event types and their payload schemas. If the catalog covers five events or fewer, the webhook system is a demo. You cannot build a reliable Slack notification, a ticket sync, or a data warehouse feed on five event types.
Onplana webhooks ship with a full event catalog covering the project lifecycle: task operations, milestone events, project changes, comment activity, and member updates. Payloads are signed with HMAC-SHA256 per webhook secret. Failed deliveries retry automatically with exponential backoff. This guide covers the architecture, signature verification, retry handling, and the three most common integration patterns.
TL;DR: Onplana webhooks are per-org or per-project HTTP callbacks that POST HMAC-SHA256 signed JSON payloads to any endpoint you control. The webhook.manage permission controls who can create and configure them. Onplana retries failed deliveries automatically. Signature verification with your webhook secret confirms payload authenticity before processing. The most common integration patterns are Slack notifications, ticket system sync, and data warehouse ingestion.
What Onplana Webhooks Are (and What They're Not)
A webhook is a server-to-server push notification. When something happens in Onplana (a task is updated, a milestone is completed, a project status changes), Onplana sends an HTTP POST request to an endpoint you control with a JSON payload describing the event.
This is different from polling. With polling, your integration sends requests to the Onplana API on a schedule ("check for changes every 5 minutes") and processes whatever has changed. With webhooks, Onplana pushes the event to you the moment it happens. For latency-sensitive integrations (like Slack notifications that should fire within seconds of a status change), polling at any reasonable interval is too slow and too expensive in API calls.
Onplana webhooks are not a full-duplex API. They deliver events; they do not accept commands. If your integration needs to read current project state in response to a webhook event, that's a separate call to the Onplana REST API using a Personal Access Token or OAuth credential. Webhooks handle the push half; the REST API handles the pull and write halves.
The permission to create and manage webhooks is webhook.manage, configurable per org role in Org Settings → Permission Matrix. By default this permission is available to users with the ADMIN role and above; your org can tighten or loosen this in the matrix.
The Event Catalog: What Fires a Webhook
Onplana fires webhook events across the full project lifecycle. Events are organized into categories.
Project events cover the project-level lifecycle: project created, updated, archived, deleted, and status changed. A status change event fires whenever the project's overall status (on track, at risk, off track) is updated, making it the standard trigger for executive dashboard integrations that track portfolio health.
Task events are the most frequently fired category: task created, updated, completed, deleted, assigned, and unassigned. Task updates carry a diff payload showing which fields changed and their before-and-after values, so your integration can react specifically to due date changes or priority escalations without processing every task mutation.
Milestone events fire on milestone created, updated, and completed. For Gantt-driven projects with external stakeholders watching milestone dates, milestone events are the right trigger for client notifications or escalation workflows.
Comment events fire when comments are added to tasks or projects. These are the basis for notification patterns: a comment from a blocked team member or a client response to a risk item triggers an immediate Slack message rather than waiting for the next daily digest.
Member events cover org and project membership changes: member invited, activated, deactivated, and role changed. These are useful for audit pipelines and for downstream systems (like a CRM or a billing system) that track which users are active in each project.
The complete event catalog with payload schemas is available in the Onplana developer documentation. Event names follow the pattern resource.action (for example, task.updated, milestone.completed, project.status_changed), so filtering in your handler is straightforward.
Payload Structure and HMAC-SHA256 Signing
Every webhook delivery from Onplana carries two headers beyond the standard HTTP headers:
X-Onplana-Event: the event type, matching theresource.actionpatternX-Onplana-Signature: an HMAC-SHA256 hash of the raw request body, computed with your webhook's secret key
The payload body is a JSON object with consistent top-level fields across all event types:
{
"event": "task.updated",
"timestamp": "2026-06-27T09:15:23Z",
"organization_id": "org_abc123",
"project_id": "proj_xyz789",
"data": { ... }
}
The data field contains the event-specific payload, including the resource's full current state and (for update events) a diff object showing changed fields.
The diagram below shows how signing works: Onplana computes the signature from the raw body before sending, and your endpoint recomputes it from the raw body it receives to verify authenticity.
To verify the signature in your endpoint handler:
const crypto = require('crypto');
function verifyOnplanaWebhook(rawBody, signatureHeader, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody) // raw bytes, not parsed JSON
.digest('hex');
// constant-time comparison prevents timing attacks
return crypto.timingSafeEqual(
Buffer.from(signatureHeader, 'hex'),
Buffer.from(expected, 'hex')
);
}
Two implementation notes:
First, compute the HMAC over the raw request body bytes, not over a re-serialized JSON object. JSON serialization is not deterministic across libraries; even a minor difference in key ordering or whitespace produces a completely different hash.
Second, use a constant-time comparison (crypto.timingSafeEqual in Node.js, hmac.compare_digest in Python, MessageDigest.isEqual in Java). A character-by-character comparison leaks timing information that an attacker can use to forge signatures incrementally.
The HMAC-SHA256 algorithm and its security properties are specified in RFC 2104.
Retry Semantics and Idempotency
Onplana considers a webhook delivery successful when your endpoint returns an HTTP 2xx status code. Any other response (3xx redirect, 4xx error, 5xx error, or a connection timeout) is treated as a failure.
On failure, Onplana retries with exponential backoff. The retry schedule is designed so that transient outages (a deployment, a brief database hiccup) do not permanently lose events. After the retry window is exhausted, the delivery is marked failed and logged in the webhook delivery history, accessible from Org Settings → Developer → Webhooks → Delivery Log.
Because Onplana retries, your endpoint must handle duplicate deliveries correctly. Each delivery carries a unique delivery_id field in the payload. Store processed delivery IDs and skip any that have already been handled:
async function handleWebhook(payload) {
if (await db.deliveries.exists(payload.delivery_id)) return; // duplicate
await db.deliveries.mark(payload.delivery_id);
// ... process the event
}
Return a 2xx response before doing any long-running work. Webhook delivery has a response timeout; if your handler tries to synchronously import 500 tasks into Jira before responding, it will time out and Onplana will retry the delivery. Accept the payload immediately, enqueue it, and process asynchronously.
Common Integration Patterns
Slack Notifications
The most common Onplana webhook integration: fire a Slack message when a milestone completes, a task is flagged as blocked, or a project status changes to "at risk."
Your handler receives the webhook payload, extracts the relevant fields (project name, task name, assigned user, new status), formats a Slack Block Kit message, and POSTs it to a Slack Incoming Webhook URL. Scope the webhook to milestone.completed and project.status_changed events to avoid flooding the channel with routine task updates.
A useful refinement: use a Slack channel per project (auto-created via Slack API when the project is created in Onplana) rather than a single shared channel. Individual project channels surface issues in context without creating a firehose that everyone mutes.
Ticket System Sync (Jira, Linear, Azure DevOps)
For engineering teams that live in a ticket system, bidirectional sync with Onplana tasks eliminates the "two places to update" problem. The Onplana-to-ticket direction is driven by webhooks.
When an Onplana task is created or updated, the webhook fires, your handler checks whether a corresponding ticket already exists (using a custom field or label you populate on ticket creation), and either creates a new ticket or updates the existing one. For task.updated events, use the diff payload to update only the changed fields rather than overwriting the full ticket.
The reverse direction (ticket updates syncing back into Onplana) requires a webhook from the ticket system. Each system has its own webhook format; the logic is symmetric: receive the ticket event, look up the corresponding Onplana task ID, and call the Onplana REST API to update the task.
Data Warehouse Ingestion
For PMO teams that run portfolio analytics in a BI tool (Power BI, Looker, Tableau), Onplana webhooks provide a low-latency data feed into your warehouse without requiring scheduled ETL jobs.
Your handler receives webhook events and writes rows to a staging table (one row per event, with a processed flag). A background job processes the staging table, upserts the Onplana entity state into your dimension tables, and updates the fact tables. This pattern handles retries gracefully (the duplicate-delivery check prevents double-inserts) and gives your BI layer near-real-time project data without requiring a polling API integration.
Registering and Managing Webhooks in Onplana
To create a webhook:
- Open Org Settings → Developer → Webhooks and click Add Webhook.
- Enter your endpoint URL. It must be a valid HTTPS URL reachable from Onplana's servers.
- Select the event types you want to receive. Selecting fewer events reduces payload volume and simplifies handler logic.
- Choose scope: org-wide (all projects) or a specific project.
- Onplana generates a webhook secret. Copy it now and store it in your application's secret manager (AWS Secrets Manager, Azure Key Vault, or similar). You will not be able to retrieve the secret after closing this screen.
- Optionally, add a description to help future maintainers understand what this webhook drives.
- Save. Onplana immediately sends a test ping to your endpoint to confirm it's reachable.
You can create multiple webhooks pointing to different endpoints for different event sets. A common pattern: one webhook for task events going to a Slack channel, a second for all events going to a data warehouse ingestion endpoint.
To rotate a webhook secret (for example, after a security incident or a key rotation schedule), delete the existing webhook and create a new one with a fresh secret. There is no in-place secret rotation; creating a new webhook ensures the old secret is invalidated immediately.
Testing Your Webhook Integration
Before wiring up a webhook to production systems, test it locally with a tool like ngrok or a similar tunnel, which gives your local development server a public HTTPS URL.
- Start your local handler on
http://localhost:3000/webhook. - Start an ngrok tunnel:
ngrok http 3000. Copy thehttps://...ngrok.ioURL. - Register a webhook in Onplana pointing to the ngrok URL.
- Use the Test Ping button in Org Settings → Developer → Webhooks to send a sample payload.
- Confirm your handler receives the payload, verifies the signature, and returns
200 OK. - Check the delivery log in Onplana to confirm the delivery was marked successful.
Once the integration is working locally, deploy to your production endpoint and update the webhook URL in Onplana. Keep the ngrok webhook as a separate registration for development testing rather than sharing the same registration with production.
For the complete Onplana API surface (REST endpoints for reading and writing project data alongside the webhook push layer), see the Onplana features overview. For the security controls around API access and token management, see the security and compliance overview. For questions about which pricing tier includes webhook management, see the Onplana pricing page.
Ready to make the switch?
Start your free Onplana account and import your existing projects in minutes.