# Webhooks

Source: https://www.geardex.app/api-docs/webhooks

API access requires the GearDex Studio plan and a scoped Studio API key.

Keep external systems synchronized with signed events emitted by Studio API writes and matching changes inside GearDex.

## Event delivery

GearDex sends an HTTP `POST` request to each subscribed endpoint. The body is JSON and the signature covers the exact raw body bytes received by your server.

Acknowledge quickly

Verify the signature, persist the event ID, and return a successful response before starting slow downstream work.

**Webhook delivery behavior**

| Rule | Value | Detail |
| --- | --- | --- |
| `Method` | POST | JSON body, User-Agent GearDex-Webhooks/1.0. |
| `Timeout` | 5 seconds | A delivery that does not respond in time counts as failed. |
| `Success` | 2xx | Any other status is recorded as a failed attempt. |
| `Attempts` | Up to 5 | Retries after 15 minutes, 1 hour, 6 hours, then 24 hours. |
| `Endpoint URL` | HTTPS | Private, local, and credential-bearing URLs are rejected. |

## Events

Subscribe each endpoint to the events it needs. Studio API writes and matching changes inside GearDex both emit events.

Gear

`gear.created` `gear.updated` `gear.deleted`

Shoots

`shoot.created` `shoot.updated` `shoot.deleted`

Maintenance

`maintenance.created` `maintenance.updated` `maintenance.deleted`

Lending

`loan.checked_out` `loan.returned` `loan.due_soon` `loan.overdue` `flag.raised`

API keys

`api_key.created` `api_key.revoked`

Testing

`webhook.test`

Business

`booking.requested` `booking.confirmed` `booking.cancelled` `quote.accepted` `contract.signed` `invoice.paid` `deliverable.delivered`

## Payload

Every delivery wraps the changed record in the same envelope. `data` carries the record fields, for example `gear_type` and `id` on gear events.

**Event body**

```json
{
  "id": "event_uuid",
  "type": "gear.updated",
  "created_at": "2026-08-02T19:48:22.000Z",
  "data": { }
}
```

## Headers

**GearDex webhook request headers**

| Header | Format | Description |
| --- | --- | --- |
| `X-GearDex-Event-Id` | `UUID` | Stable identifier for idempotency and replay protection. |
| `X-GearDex-Event-Type` | `string` | Event name such as gear.updated, shoot.created, or webhook.test. |
| `X-GearDex-Webhook-Id` | `UUID` | The configured Studio webhook endpoint that received the event. |
| `X-GearDex-Timestamp` | `Unix time` | Timestamp included in the signed payload string. |
| `X-GearDex-Signature` | `HMAC-SHA256` | Signature formatted as t=<timestamp>,v1=<hex digest>. |

## Verify signatures

Compute the expected digest from `<timestamp>.<rawBody>` and the endpoint's signing secret. Compare signatures with a constant-time function.

**Verify signature (Node.js)**

```js
import crypto from "node:crypto"

export function verifyGearDexWebhook({ rawBody, signatureHeader, signingSecret }) {
  const parts = Object.fromEntries(
    signatureHeader.split(",").map((part) => part.split("="))
  )
  const timestamp = parts.t
  const signature = parts.v1
  if (!timestamp || !signature) return false

  const expected = crypto
    .createHmac("sha256", signingSecret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex")

  if (signature.length !== expected.length) return false

  return crypto.timingSafeEqual(
    Buffer.from(signature, "hex"),
    Buffer.from(expected, "hex")
  )
}
```

Prevent replay

Reject stale timestamps and store processed event IDs. Signature verification alone does not prevent a valid delivery from being replayed.

## Receive an event

**Next.js route handler**

```ts
import { NextResponse } from "next/server"
import { verifyGearDexWebhook } from "@/site/lib/geardex-webhooks"

export async function POST(request) {
  const rawBody = await request.text()
  const verified = verifyGearDexWebhook({
    rawBody,
    signatureHeader: request.headers.get("x-geardex-signature") || "",
    signingSecret: process.env.GEARDEX_WEBHOOK_SECRET,
  })

  if (!verified) {
    return NextResponse.json({ error: "Invalid signature" }, { status: 401 })
  }

  const event = JSON.parse(rawBody)
  // Enqueue work using event.id as the idempotency key.
  return NextResponse.json({ received: event.id })
}
```

## Retries and replay

GearDex retries failed deliveries on a backoff schedule. Studio owners can inspect and replay recent deliveries from Settings, so consumers should treat the event ID as an idempotency key.

- Return a 2xx response only after the event is safely accepted.
- Expect the same event ID to arrive more than once.
- Keep signing secrets outside source control.
- Rotate an endpoint secret after suspected exposure.

## Endpoint templates

GearDex offers starting templates when you add an endpoint in Settings. Each one preselects the recommended events.

Automation Intake

Zapier / Make

Send new and changed shoot work into no-code automation flows.

`shoot.created``shoot.updated``gear.updated`

CRM Handoff

Client Ops

Notify a CRM or client workspace when shoots move forward.

`shoot.created``shoot.updated``shoot.deleted`

Inventory Sync

Asset Systems

Mirror gear additions, edits, and removals into external asset tools.

`gear.created``gear.updated``gear.deleted`

Maintenance Alerts

Ops Queue

Push service records into shop queues, Slack-style alerts, or work orders.

`maintenance.created``maintenance.updated``maintenance.deleted`

Lending Alerts

Slack / Ops

Post check-outs, returns, overdue gear, and flagged issues to a team channel.

`loan.checked_out``loan.returned``loan.due_soon``loan.overdue``flag.raised`

Security Audit

Access Review

Track Studio API key creation and revocation in an external audit log.

`api_key.created``api_key.revoked`

Need the raw change feed instead?

 [Browse the endpoint reference](https://www.geardex.app/api-docs/endpoints)
