Skip to main content

Documentation Index

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

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

Spotzee delivers events to your endpoints via signed HTTP POST. Verify the signature on every incoming webhook before you trust the payload.

Configure an endpoint

Create or rotate webhook endpoints from Settings → Webhooks in the Spotzee app, or via the API. Each endpoint has:
  • A target URL (must be https://)
  • A list of subscribed event types
  • A signing secret (issued on creation, rotatable)

The signature header

Every webhook request carries a Spotzee-Signature header.
POST /your-webhook HTTP/1.1
Spotzee-Signature: t=1735689600,v1=8a4d3c6f9b…
Content-Type: application/json
ComponentMeaning
t=Unix timestamp (seconds) when Spotzee signed the request
v1=Hex-encoded HMAC-SHA256 of <t>.<raw_body> using your endpoint’s signing secret

Verify the signature

1

Read the timestamp and signature

Parse the Spotzee-Signature header into t and v1 values.
2

Reject stale timestamps

Reject the request if t is more than 5 minutes off your current clock — that’s the replay-protection window.
3

Compute the expected HMAC

Concatenate <t>.<raw_body> (period separator) and HMAC-SHA256 it with your signing secret. Hex-encode the result.
4

Compare with constant-time equality

Use a constant-time comparison (e.g. crypto.timingSafeEqual in JavaScript, hmac.compare_digest in Python). Reject on mismatch.

Example — JavaScript

import crypto from 'node:crypto'

export function verifyWebhook(rawBody, header, secret) {
  const parts = Object.fromEntries(
    header.split(',').map(p => p.split('=', 2))
  )
  const t = Number(parts.t)
  if (Math.abs(Date.now() / 1000 - t) > 300) return false

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${parts.t}.${rawBody}`)
    .digest('hex')

  const a = Buffer.from(expected, 'hex')
  const b = Buffer.from(parts.v1, 'hex')
  return a.length === b.length && crypto.timingSafeEqual(a, b)
}
Sign the raw request body, not a parsed-and-re-stringified version. JSON re-encoding can change byte-for-byte content and break the signature.

Errors

If signature verification fails on Spotzee’s side (for example, you flipped the secret without rotating), Spotzee surfaces webhook_signature_invalid:
HTTPcodeWhen
400webhook_signature_invalidHMAC didn’t match the expected value

Rotate the signing secret

1

Add a new signing secret

Generate a new secret in Settings → Webhooks. Both the old and new secrets are valid for the rotation window.
2

Update your verifier

Accept signatures verified against either secret while you cut over.
3

Promote the new secret

Confirm traffic is verified by the new secret, then revoke the old one.

Retry policy

Spotzee retries 5xx responses and connection errors with exponential backoff:
AttemptDelay
1immediate
230 seconds
35 minutes
430 minutes
52 hours
66 hours
724 hours (final)
After the final attempt, the delivery is marked failed and surfaced in Settings → Webhooks → Deliveries. Replies with any 2xx confirm receipt; 4xx is a permanent failure and stops retrying.

Idempotency on the receiving side

Webhooks are at-least-once. Stamp each event with its event_id and discard duplicates on your side, or use the same idempotency-key pattern in your own database to make the handler safe to call twice.

Next steps

Errors

Status codes and the code catalogue.

Authentication

Choose the right key type for outgoing API calls.