Agent skill

Firstbase Webhooks Skill

Receive and authenticate Firstbase webhooks. Use when setting up a Firstbase webhook endpoint with Custom header authentication, checking the secret header, debugging 401s, or handling order, return order and inventory order notifications with eventType CREATED, UPDATED or DELETED.

Install this skill

npx skills add hookdeck/webhook-skills --skill firstbase-webhooks


Firstbase (remote-employee equipment) sends a thin JSON webhook when an order, return order or inventory order is created, updated or deleted. The notification carries an eventType and the resource id; you fetch the details from the Firstbase API. Webhooks are not signed. You authenticate them with a secret header that Firstbase adds when you choose Custom header authentication.

Use Custom header authentication, not Auth token or OAuth.

Auth token doesn't document which header it sends, and OAuth client credentials need a token server, so this skill covers only Custom header.

When to Use This Skill

  • How do I receive Firstbase webhooks?
  • How do I authenticate Firstbase webhook requests with a custom header?
  • Does Firstbase sign webhook payloads? (No: use the secret header)
  • How do I handle CREATED, UPDATED and DELETED order notifications?
  • Why does my Firstbase webhook endpoint return 401?

Verification (core)

const crypto = require('crypto');

// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
  const ha = crypto.createHash('sha256').update(String(a)).digest();
  const hb = crypto.createHash('sha256').update(String(b)).digest();
  return crypto.timingSafeEqual(ha, hb);
}

function verifyFirstbaseRequest(headers,
    name = process.env.FIRSTBASE_WEBHOOK_HEADER || 'Authorization',
    secret = process.env.FIRSTBASE_WEBHOOK_SECRET) {
  if (!secret) return false;                    // never accept when unconfigured
  const received = headers[name.toLowerCase()]; // Node lowercases header names
  return typeof received === 'string' && received !== '' && safeEqual(received, secret);
}
  • The header name is whatever you typed in Firstbase, so it is read from FIRSTBASE_WEBHOOK_HEADER (default Authorization) and looked up case-insensitively.
  • FIRSTBASE_WEBHOOK_SECRET is the exact, whole header value, including any Bearer prefix you typed. Compare the whole value; don't strip or parse a prefix.
  • Python: hmac.compare_digest(received.encode(), secret.encode()) on bytes, never str (non-ASCII str raises).
  • Missing header or mismatch: 401. Bad body: 400. Handled: any 2xx.
  • Read the body raw (express.raw({ type: '*/*' })) and authenticate before parsing it.

Security note: a static shared credential authenticates the sender, not the body, and gives no replay protection (no signature, no timestamp). Use HTTPS and rotate the credential if it leaks.

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

Event Types

Firstbase sends one notification per change, per resource type (orders, return orders, inventory orders). The eventType field is one of:

eventTypeSent When
CREATEDAn order, return order or inventory order is created
UPDATEDOne of those resources changes
DELETEDOne of those resources is deleted

The body is thin: the eventType plus the id of the resource. Fetch the full record from the Firstbase API.

Environment Variables

FIRSTBASE_WEBHOOK_HEADER=Authorization       # header name from Firstbase Custom header auth
FIRSTBASE_WEBHOOK_SECRET=your-header-value   # exact, whole value, incl. any "Bearer " prefix

Local Development

Firstbase can't reach localhost. Tunnel with the Hookdeck CLI (no account required):

npx hookdeck-cli listen 3000 firstbase --path /webhooks/firstbase

Use the printed URL as the webhook URL in Firstbase. The endpoint must be HTTPS with a valid certificate and accept POST.

Resources


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 6, 2026

View on GitHub →