Test Autodesk Platform Services webhooks locally
Receive Autodesk Platform Services webhook events on localhost with a Horizon tunnel, and verify the x-adsk-signature header in Next.js.
Receive Autodesk Platform Services (APS) webhook events on your laptop while you build, with a public HTTPS URL APS can reach.
Horizon has no Autodesk integration. The APS Webhooks service sends events to a public URL, and Horizon provides that URL. APS has no dashboard page for webhooks. You register them through the Webhooks API.
Before you begin
- Node.js 18 or later
- A Horizon account and the CLI (see Getting started)
- A reserved subdomain for
-s. Reserve one on the Subdomains page. - An APS app, and an access token with the
data:readanddata:writescopes - A Next.js app that uses the App Router and runs on port 3000
Start your app
APS signs payloads only when you register a secret token. It then computes an HMAC-SHA1 hex digest of the payload with that token and sends sha1hash=<hex digest> in the x-adsk-signature header. Without a token, APS sends no signature.
Create a route handler. Read the raw body with request.text(), because the signature covers the exact bytes.
import { createHmac, timingSafeEqual } from "node:crypto";
export async function POST(request: Request) {
const secret = process.env.APS_WEBHOOK_SECRET;
if (!secret) {
return new Response("Missing APS_WEBHOOK_SECRET", { status: 500 });
}
const body = await request.text();
const received = request.headers.get("x-adsk-signature") ?? "";
const expected = `sha1hash=${createHmac("sha1", secret).update(body).digest("hex")}`;
const receivedBuffer = Buffer.from(received);
const expectedBuffer = Buffer.from(expected);
const isValid =
receivedBuffer.length === expectedBuffer.length &&
timingSafeEqual(receivedBuffer, expectedBuffer);
if (!isValid) {
return new Response("Invalid signature", { status: 401 });
}
console.log("Received APS webhook:", body);
return new Response("ok", { status: 200 });
}APS expects a 2xx response within 6 seconds. Keep the handler fast.
Pick a random alphanumeric secret between 32 and 64 characters, and store it:
APS_WEBHOOK_SECRET=replace-with-a-32-to-64-character-alphanumeric-stringStart the app:
npm run devStart a tunnel
In a second terminal, open a tunnel to port 3000 on a subdomain you reserved, with -s:
hrzn tunnel http://localhost:3000 -s my-aps-appHORIZON: Tunnel connected
URL https://my-aps-app.hrzn.run (reserved)
Forwarding http://localhost:3000
Request log https://hrzn.run/dashboard/tunnels/my-aps-appYour public URL is https://my-aps-app.hrzn.run. Without -s the subdomain is random and changes on every run, so a registered hook would point at a dead URL after a restart. Reserved subdomains are a paid feature, see Pricing.
Register the secret token
Send the secret to the Webhooks API with POST /webhooks/v1/tokens. Replace <ACCESS_TOKEN> with your access token and the token value with your secret.
curl -X POST "https://developer.api.autodesk.com/webhooks/v1/tokens" \
-H "Authorization: Bearer <ACCESS_TOKEN>" \
-H "Content-Type: application/json" \
-d '{ "token": "replace-with-a-32-to-64-character-alphanumeric-string" }'From now on, callbacks carry the x-adsk-signature header.
Create the hook
Create a hook for a Data Management event. This example listens for dm.version.added in one folder. Set callbackUrl to your tunnel URL plus the route path, and scope.folder to the URN of the folder to watch.
curl -X POST "https://developer.api.autodesk.com/webhooks/v1/systems/data/events/dm.version.added/hooks" \
-H "Authorization: Bearer <ACCESS_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"callbackUrl": "https://my-aps-app.hrzn.run/api/webhooks/autodesk",
"scope": { "folder": "<FOLDER_URN>" }
}'A successful call returns 201 Created with the hook's details.
Trigger an event
APS has no resend tool. Do what the event watches for. For dm.version.added, upload a new file or a new version of a file to the folder. APS then calls your URL.
Check it works
After you add a version to the folder, the Horizon terminal prints one line:
POST 200 /api/webhooks/autodeskYour app terminal prints the payload as Received APS webhook: {...}.
If the line shows [401], see Troubleshooting.
Troubleshooting
The signature doesn't match
The Horizon line shows [401]. Check these in order:
APS_WEBHOOK_SECRETis exactly the token you sent toPOST /webhooks/v1/tokens. Restartnpm run devafter you edit.env.local.- The handler hashes the raw body from
request.text(). Parsing the JSON first and hashing the object gives a different value. - You compare against
sha1hash=followed by the hex digest, not the digest alone.
There is no x-adsk-signature header
You didn't register a secret token, or the token call failed. Send the token to POST /webhooks/v1/tokens and check for a success response.
The hook stops delivering
APS retries a failed callback four times, and the last retry is at least 48 hours after the first attempt. If the last retry fails, APS disables the hook. A failure is a non-2xx status, or no response within 6 seconds. Fix the handler, then create or reactivate the hook.
The URL changed after a restart
You started the tunnel without -s, so Horizon gave you a new random subdomain. The hook still calls the old URL. Restart with -s my-aps-app, and create the hook again if the URL differs.
Next steps
- Read APS's guide to verifying the payload signature.
- See the Webhooks API reference for other systems and events.
- Reserve a subdomain so your URL never changes: see pricing.
Test X (Twitter) webhooks locally
Receive X Account Activity API webhook events on localhost with a Horizon tunnel, and pass the CRC check and signature verification.
Test Box webhooks locally
Receive Box V2 webhook events on localhost with a Horizon tunnel, and verify the primary and secondary signatures in a Next.js route handler.