Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.arcuserp.com/llms.txt

Use this file to discover all available pages before exploring further.

What you will build

A local HTTP server that receives Arcus webhook events, verifies the signature, and logs the event type. You will then trigger a real event from the Arcus app and see it arrive.

Prerequisites

  • An API key with webhooks:write scope
  • Node.js 18 or later (or adapt the examples to your stack)
  • ngrok or similar tunnel (to expose localhost to the internet)

Step 1: Start a local server

npm install express
// server.js
import express from 'express';
import crypto from 'crypto';

const app = express();
const WEBHOOK_SECRET = process.env.ARCUS_WEBHOOK_SECRET;

// Use raw body for signature verification
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['arcus-signature'];

  if (!signature) {
    return res.status(400).send('Missing signature');
  }

  // Verify the signature
  const [tPart, vPart] = signature.split(',');
  const timestamp = tPart.replace('t=', '');
  const receivedSig = vPart.replace('v1=', '');

  const payload = `${timestamp}.${req.body.toString()}`;
  const expected = crypto
    .createHmac('sha256', WEBHOOK_SECRET)
    .update(payload)
    .digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(receivedSig))) {
    return res.status(400).send('Invalid signature');
  }

  const event = JSON.parse(req.body);
  console.log(`Received event: ${event.type} (${event.id})`);
  console.log(JSON.stringify(event.data.object, null, 2));

  res.status(200).json({ received: true });
});

app.listen(4242, () => console.log('Listening on :4242'));
ARCUS_WEBHOOK_SECRET=whsec_test_placeholder node server.js

Step 2: Expose with ngrok

ngrok http 4242
Copy the https:// forwarding URL (e.g. https://abc123.ngrok-free.app).

Step 3: Register the endpoint

curl -X POST https://api.arcuserp.com/v1/entities/$ARCUS_ENTITY_ID/webhooks \
  -H "Authorization: Bearer $ARCUS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://abc123.ngrok-free.app/webhook",
    "events": ["order.confirmed", "order.created"],
    "description": "Local dev webhook"
  }'
Save the webhook_secret from the response:
export ARCUS_WEBHOOK_SECRET=whsec_...
Restart your server with the real secret.

Step 4: Trigger an event

In the Arcus app, create a new draft order and confirm it. Within a few seconds, your terminal should show:
Received event: order.confirmed (evt_01H...)
{
  "id": "ord_01H...",
  "object": "order",
  "document_type": "sales_order",
  "status": "confirmed",
  ...
}

Step 5: Handle events safely

In production, follow these patterns:
app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
  // ... verify signature (same as above) ...

  const event = JSON.parse(req.body);

  // 1. Respond immediately -- don't block on processing
  res.status(200).json({ received: true });

  // 2. Deduplicate using event.id
  const alreadyProcessed = await db.webhookEvents.findUnique({ where: { id: event.id } });
  if (alreadyProcessed) return;

  await db.webhookEvents.create({ data: { id: event.id } });

  // 3. Process asynchronously
  await queue.enqueue({ type: event.type, data: event.data.object });
});

Next steps

  • Read Webhooks for the full event catalog and retry behavior
  • Add more event types to your subscription in Settings > Developers > Webhooks
  • Set up monitoring to alert on delivery failures (Arcus retries 6 times over 24 hours)