Gareth Wilson Gareth Wilson

Guide to Onshape Webhooks: Features and Best Practices

Published


Onshape webhooks tell your application when something changes in a CAD document: a part studio is edited, a version or revision is created, a release moves through a workflow, a translation finishes, a comment is posted, or a user is added to the company. If you build PLM, ERP, manufacturing or review tools on top of Onshape, webhooks let you react to those changes instead of polling the REST API.

This guide covers how Onshape webhooks are registered and validated, how signature keys work, and how to build a receiver that stays registered and verifies every notification.

What are Onshape webhooks?

An Onshape webhook is a registration your application creates with the REST API. It names the events you want, the scope they apply to (a company, a document or an OAuth client) and the URL to notify. Onshape then sends each notification as an HTTP POST with a JSON body.

Signing is a separate, Enterprise company setting. Once an administrator generates signature keys, every notification carries X-onshape-webhook-timestamp and one signature header per key: X-onshape-webhook-signature-primary and, if a secondary key exists, X-onshape-webhook-signature-secondary. Each signature is the base64 HMAC-SHA256 of the timestamp value, a period and the raw body.

Onshape webhook features

FeatureDetails
ConfigurationREST API: POST /api/v6/webhooks on cad.onshape.com, or your company domain for Enterprise
ScopecompanyId, documentId or clientId, depending on the event
Registration checkA webhook.register trial that must return HTTP 200
CleanupWebhooks are removed after inactivity unless isTransient is false
Endpoint URLHTTP or HTTPS. HTTPS needs a certificate from a CA Onshape recognises
Signature headersX-onshape-webhook-signature-primary and -secondary, standard base64
Timestamp headerX-onshape-webhook-timestamp, when the webhook was sent (format not documented)
Signed content<timestamp>.<raw body>
SecretsPrimary and secondary keys, set by an administrator in company settings
Basic authOptional, fills the Authorization header
Event fieldevent in the body
Message IDmessageId in the body
RetriesNot documented

Common events

The full list lives in the API Explorer, under createWebhook > Callbacks. These are the ones most integrations start with:

EventFires when
onshape.model.lifecycle.changedA change is made to a model
onshape.model.lifecycle.createversionA new version of a document is created
onshape.model.lifecycle.metadataPart or element metadata is modified
onshape.model.translation.completeA translation request completes
onshape.document.lifecycle.createdA document is created
onshape.document.lifecycle.statechangeA document moves to ACTIVE, TRASH or DELETED
onshape.revision.createdA revision is created
onshape.workflow.transitionA revision or release package moves through workflow states
onshape.comment.create, .update, .deleteA comment is created, updated or deleted
onshape.task.created, .updated, .deleted, .transitionA task changes
onshape.team.addmember, .removemember, .created, .deletedA team changes
onshape.user.lifecycle.updateappsettingsA user's application settings change
webhook.register, webhook.ping, webhook.unregisterSent automatically when a webhook is registered, pinged or removed

Most events are registered against a companyId, which covers all present and future documents the company owns. onshape.document.lifecycle.statechange needs a documentId and a documentState, and onshape.user.lifecycle.updateappsettings needs your OAuth app's clientId.

Notifications share one shape. Here is Onshape's onshape.document.lifecycle.statechange example:

{
  "timestamp": "2024-05-05T23:46:29.284-0500",
  "event": "onshape.document.lifecycle.statechange",
  "workspaceId": "000000000000000000000000",
  "elementId": "000000000000000000000000",
  "webhookId": "000000000000000000000000",
  "messageId": "000000000000000000000000",
  "data": "Some data",
  "documentId": "000000000000000000000000",
  "versionId": "000000000000000000000000",
  "documentState": "TRASH"
}

Notifications carry IDs rather than the changed data, so most handlers call the REST API to read the document, element or revision they're told about.

Setting up Onshape webhooks

  1. Make your endpoint return HTTP 200 before you register anything. The webhook.register trial can arrive before the registration call returns.
  2. As a company administrator, click your account icon, then Company/Classroom/Enterprise settings > Webhooks. Under Webhook signatures, generate a primary key, click Save changes and copy the key to your server, for example as ONSHAPE_WEBHOOK_SECRET.
  3. Register the webhook:
curl -X POST "https://cad.onshape.com/api/v6/webhooks" \
  -H "Authorization: Basic CREDENTIALS" \
  -H "Content-Type: application/json" \
  -d '{
    "companyId": "000000000000000000000000",
    "events": ["onshape.model.lifecycle.createversion"],
    "options": { "collapseEvents": false },
    "isTransient": false,
    "url": "https://your-endpoint.example.com/webhooks/onshape"
  }'
  1. Confirm your endpoint received webhook.register. Call POST /api/v6/webhooks/{webhookId}/ping at any time to send a webhook.ping.

Get the companyId from findCompany (the id field, not address.id or ownerId), or pass a documentId the company owns and let Onshape infer it.

If you receive Onshape webhooks through Hookdeck, use the Hookdeck source URL as the webhook url and create a source with the Onshape type. The source asks you to:

Onshape signature keys are an Enterprise company setting. Paste the primary key; only the primary signature is verified.

The Onshape source accepts POST only, which is how Onshape delivers notifications. Hookdeck returns HTTP 200 to the webhook.register trial, so registration completes. Onshape doesn't say whether the trial is signed. If it isn't, Hookdeck records it as failed verification, which doesn't affect the registration.

Securing Onshape webhooks

Read X-onshape-webhook-timestamp and X-onshape-webhook-signature-primary. Build the message from the timestamp value exactly as received, a period and the raw body bytes. Compute an HMAC-SHA256 with the primary key, base64-encode it, and compare it with the header in constant time. Base64 is case-sensitive, so compare it exactly.

const crypto = require('crypto');
const express = require('express');

function verifyOnshapeSignature(rawBody, signatureHeader, timestampHeader, primaryKey) {
  if (!signatureHeader || !timestampHeader || !primaryKey) return false;

  const message = Buffer.concat([Buffer.from(timestampHeader, 'latin1'), Buffer.from('.'), rawBody]);
  const expected = crypto.createHmac('sha256', primaryKey).update(message).digest('base64');

  const received = Buffer.from(signatureHeader, 'utf8');
  const computed = Buffer.from(expected, 'utf8');
  return received.length === computed.length && crypto.timingSafeEqual(received, computed);
}

const app = express();

app.post('/webhooks/onshape', express.raw({ type: '*/*' }), (req, res) => {
  const primaryKey = process.env.ONSHAPE_WEBHOOK_SECRET;
  if (!primaryKey) return res.sendStatus(500); // fail closed

  const valid = verifyOnshapeSignature(
    req.body,
    req.get('x-onshape-webhook-signature-primary'),
    req.get('x-onshape-webhook-timestamp'),
    primaryKey
  );
  if (!valid) return res.sendStatus(401);

  const notification = JSON.parse(req.body.toString('utf8'));
  // Deduplicate on notification.messageId, route on notification.event, then process asynchronously.
  res.sendStatus(200);
});

The same check in Python:

import base64
import hashlib
import hmac


def verify_onshape_signature(raw_body: bytes, signature_header, timestamp_header, primary_key: str) -> bool:
    if not signature_header or not timestamp_header or not primary_key:
        return False
    message = timestamp_header.encode("latin-1") + b"." + raw_body
    expected = base64.b64encode(hmac.new(primary_key.encode("utf-8"), message, hashlib.sha256).digest())
    return hmac.compare_digest(signature_header.encode("utf-8"), expected)


# In FastAPI: raw_body = await request.body()
# verify_onshape_signature(raw_body, request.headers.get("x-onshape-webhook-signature-primary"),
#                          request.headers.get("x-onshape-webhook-timestamp"),
#                          os.environ["ONSHAPE_WEBHOOK_SECRET"])

Onshape's own Node sample compares with ===, which isn't constant-time, and its Java sample signs messageBody.getBytes(), which uses the platform's default charset. Hashing the raw bytes avoids both problems.

Onshape documents no timestamp tolerance and no format for X-onshape-webhook-timestamp, so the code doesn't check its age. Hookdeck applies no timestamp tolerance either, and its retries and replays resend the original headers. If you add a window, confirm the header's format from a real notification first.

To check your verifier, use the key onshape-guide-primary-key, an X-onshape-webhook-timestamp of 1761480000000 (an illustrative value, since the format isn't documented) and this exact body:

{"event":"webhook.ping","webhookId":"000000000000000000000000","messageId":"000000000000000000000000"}

The header should be:

X-onshape-webhook-signature-primary: q32GmDmVgQ1OtfvL7KLHSwZ9gQdlxLXejSSQo6tRyxU=

Make Onshape webhooks production-ready. Hookdeck Event Gateway answers the webhook.register trial with a 200, verifies the primary signature on every notification, and queues each CAD event until your service has processed it.

Onshape webhook limitations and pain points

Signing needs an Enterprise administrator

The Problem: Notifications arrive with no signature headers.

Why It Happens: Signature keys are a company setting, and the alert on Hookdeck's Onshape source describes them as an Enterprise setting. A webhook registered by an app in an account without keys is delivered unsigned.

Workarounds:

  • Ask a company administrator to generate a primary key under Company/Classroom/Enterprise settings > Webhooks.
  • Where signing isn't available, turn on webhook basic authentication, which fills the Authorization header, and check it on your endpoint.
  • Re-read anything important from the REST API, which needs your own credentials.

How Hookdeck Can Help: Hookdeck's Onshape source verifies the primary signature. Unsigned notifications are marked as failed verification in the request log, so you can see straight away that keys aren't set.

Rotation with two keys, verified on one header

The Problem: Verification fails after an administrator swaps keys.

Why It Happens: Onshape signs with each key into its own header and says that "If either key matches, it will be accepted as valid by Onshape." Hookdeck's Onshape source, and the code above, check the primary header only. If the key you hold is now in the secondary slot, the primary signature no longer matches.

Workarounds:

  • During rotation, always paste the key that's currently in the primary slot.
  • If you verify on your own server, check the secondary header with the secondary key as a fallback while you rotate.

How Hookdeck Can Help: Update the source's key at the moment the primary key changes. Notifications that arrive in between are kept in the request log, marked as failed verification, so you know which ones to check.

The registration trial and silent cleanup

The Problem: A webhook registration disappears, either straight away or weeks later.

Why It Happens: If the webhook.register trial doesn't get HTTP 200, Onshape cancels the registration. The trial can arrive before your registration call returns. Webhooks created without isTransient=false are cleaned up after a period of inactivity.

Workarounds:

  • Deploy and test the endpoint before registering, and return 200 for webhook.* events.
  • Set isTransient=false and unregister webhooks you no longer need.
  • List your webhooks periodically with GET /api/v6/webhooks and re-register any that are missing.

How Hookdeck Can Help: A Hookdeck source answers every request with 200, so a deploy on your side can't fail the trial.

No documented retries

The Problem: Notifications sent during an outage may be lost, and a webhook can stop delivering.

Why It Happens: Onshape doesn't publish a retry policy. Its docs say that it keeps delivering until the application "either returns an error in response to a notification for the webhook or fails to respond at all for an extended period of time."

Workarounds:

  • Return 200 quickly and do the work asynchronously.
  • Use the REST API to catch up after downtime, for example by listing versions or revisions created since the last event you processed.

How Hookdeck Can Help: Hookdeck acknowledges each notification as soon as it arrives and stores it, then delivers to your service with retries you configure, so your errors aren't what Onshape sees.

Best practices

Verify against the raw body

Onshape signs the payload exactly as sent. In Express, use express.raw({ type: '*/*' }). In Next.js, use Buffer.from(await request.arrayBuffer()). In FastAPI, use await request.body().

Deduplicate on messageId

Each notification has a messageId. Record the IDs you've processed and skip repeats. See our guide to webhook idempotency and Hookdeck's deduplication.

Use HTTPS

Onshape accepts HTTP URLs, but its help page tells administrators to send webhooks to HTTPS endpoints because the data isn't encrypted otherwise. Use a certificate from a recognised CA, since self-signed certificates make delivery fail.

Expect high volume from model events

onshape.model.lifecycle.changed fires when a change is made to a model, so it may arrive often while someone is editing. Register only the events you need, and consider createversion or revision.created when you only care about milestones.

Conclusion

Onshape webhooks are registered by API, validated with a webhook.register trial, and signed only when an Enterprise administrator has set keys. Sign the timestamp, a period and the raw body with the primary key, keep the registration alive with isTransient=false, and plan for catch-up through the REST API because retries aren't documented.

Hookdeck Event Gateway sits in front of your receiver, checks every Onshape signature, and holds each notification until your service has processed it.

Get started with Hookdeck for free and handle Onshape webhooks reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.