Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.stella-commerce.com/llms.txt

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

Stella emits HMAC-SHA256-signed webhook events for every meaningful state change. You point us at a URL, pick events, and your system gets pinged on every order, customer, inventory, or product change. The wire format is Stripe-style: t=<unix>,v1=<hex> in the X-Stella-Signature header, signed payload is <timestamp>.<rawBody>.

Register an endpoint

In the dashboard at Settings → Webhooks:
  1. Click Add endpoint.
  2. URL — your receiver (https://kasa.com/api/stella/webhooks).
  3. Pick events. The v1 taxonomy:
    • order.created, order.paid, order.fulfilled, order.cancelled, order.refunded
    • customer.created, customer.updated
    • inventory.updated
    • product.updated
  4. Save. The signing secret (whsec_…) is shown once — copy it.

Verify a delivery

Pseudocode (real implementation: see @stella/webhooks):
const signature = req.headers['x-stella-signature']
const [tPart, v1Part] = signature.split(',')
const timestamp = tPart.split('=')[1]
const expected  = v1Part.split('=')[1]

// Reject anything older than 5 minutes (replay defense).
if (Math.abs(Date.now() / 1000 - parseInt(timestamp, 10)) > 300) {
  return res.status(400).end()
}

const signedPayload = `${timestamp}.${rawBody}`
const computed = crypto
  .createHmac('sha256', WHSEC)
  .update(signedPayload)
  .digest('hex')

if (!crypto.timingSafeEqual(
      Buffer.from(computed),
      Buffer.from(expected))) {
  return res.status(400).end()
}

const event = JSON.parse(rawBody)
// Handle event.type, event.data, event.merchant_id, ...
res.status(200).end()
Use @stella/webhooks (PR #81) once published — the manual implementation above is just to show the wire format.

Retry policy

Failed deliveries (any non-2xx, or timeout > 30s) are retried with exponential backoff:
attempt 1 → +1 minute
attempt 2 → +5 minutes
attempt 3 → +30 minutes
attempt 4 → +2 hours
attempt 5 → +12 hours
After the fifth failure, the delivery is marked failed and shown red in the dashboard’s delivery log. You can replay it from there.

Delivery log

Every event surfaces:
  • Event type, merchant ID, payload hash
  • Each attempt’s status, response body, response time, ACAO header
  • Replay button per delivery
Replays carry the original timestamp + signature — your verifier should still accept them inside the 5-minute skew window. (If you’ve gone past the skew, the dashboard’s “Replay with fresh signature” mints a new signature stamp.)

Idempotency on your end

Every event carries an event_id that’s stable across replays. Your receiver should:
if (await alreadyProcessed(event.event_id)) {
  return res.status(200).end()  // ack but don't double-process
}
await process(event)
await markProcessed(event.event_id)
res.status(200).end()
This protects against duplicate deliveries from retries or operator replays.