Skip to main content

The app kit (@arc/app-kit)

The app kit has what an app needs to talk to ARC safely, with no runtime dependencies (Node 18+):

ExportUse it to
verifyWebhookCheck a webhook's signature and timestamp, and read its event and environment.
verifySessionTokenCheck the session token an embedded admin extension gets from App Bridge.
APP_EVENT_TOPICS, AppEvent, isTopicType the events you receive.
APP_MANIFEST_JSON_SCHEMAValidate your manifest before you publish a version.
createArcAppHandlerServe the two routes every app needs (webhooks, session tokens) on a Node http server. See the CLI.

The kit also ships the arc CLI (arc app init | dev | deploy): see The arc app CLI. The API's conformance suite checks the kit against ARC's own signers.

Verify a webhook​

Hash the raw request body. A re-serialised copy of the parsed JSON has different bytes and fails.

import { verifyWebhook, isTopic } from '@arc/app-kit';

app.post('/arc/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
const result = verifyWebhook({
rawBody: req.body, // a Buffer
headers: req.headers,
secret: process.env.ARC_CLIENT_SECRET!,
});
if (!result.ok) return res.status(401).end(); // result.reason says why
if (alreadyHandled(result.event.id)) return res.status(200).end(); // dedupe on the event id
if (isTopic(result.event, 'app.uninstalled')) {
// forget the installation
}
res.status(200).end();
});
  • The signature is v1=<hex HMAC-SHA256(secret, "<X-ARC-Timestamp>.<raw body>")>. During a client secret rotation ARC sends two v1= values; pass secret: [current, previous] to accept both.
  • A timestamp more than 300 seconds from your clock is refused (toleranceSeconds changes it).
  • result.environment is test for a test-mode organization (every dev tenant), otherwise live.
  • Branch on event.type. An event can come from a newer API version than your kit knows, so treat an unknown type as "ignore".

Verify a session token​

An admin extension gets a 60-second token from App Bridge and sends it to your backend:

import { verifySessionToken } from '@arc/app-kit';

const claims = verifySessionToken(token, {
secret: process.env.ARC_CLIENT_SECRET!,
clientId: process.env.ARC_CLIENT_ID!,
});
if (!claims) return res.status(401).end();
// claims.dest = the organization, claims.sub = the user,
// claims.scopeRepIds = the reps whose records this user may see (null = all)

Only HS256 tokens signed with your client secret are accepted; alg: none and every other algorithm are refused. Reject a jti you have already seen.

Validate a manifest​

APP_MANIFEST_JSON_SCHEMA is JSON Schema (2020-12), generated from ARC's own manifest schema. Use any validator, or point your editor at it. ARC validates again when you publish a version: scope and topic names and redirect-URI rules are checked there.