Skip to main content

Documentation Index

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

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

The WebhooksService class (accessed via client.webhooks) manages outbound webhook endpoints and provides access to the event delivery log.
Secret key required. All webhook management endpoints require a secret key (tybrite_sk_*). Publishable keys return 403 Forbidden.

Overview

Webhooks allow your systems to receive real-time push notifications when events occur in your store — eliminating the need to poll the API. Every delivery is HMAC-signed so you can verify authenticity before acting on the payload. Supported event categories: Orders, Payments, Customers, Inventory, Cart, Gift Cards, Promotions. Delivery contract:
  • At-least-once — your endpoint must be idempotent. Use event.id for deduplication.
  • HMAC-signed — verify with the signing_secret returned at endpoint creation.
  • Best-effort ordering — use event.created_at for sequencing, not arrival order.

Signature Verification

Every webhook delivery includes the header:
X-Tybrite-Signature: t=<unix_timestamp>,v1=<hmac_sha256_hex>
Verify it in your endpoint handler:
import { createHmac, timingSafeEqual } from 'crypto';

function verifyTybriteWebhook(
  rawBody: string,
  signature: string,
  signingSecret: string
): boolean {
  const [tPart, v1Part] = signature.split(',');
  const timestamp = tPart.replace('t=', '');
  const receivedSig = v1Part.replace('v1=', '');

  // Reject replays older than 5 minutes
  if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) return false;

  const payload = `${timestamp}.${rawBody}`;
  const expected = createHmac('sha256', signingSecret).update(payload).digest('hex');

  return timingSafeEqual(Buffer.from(receivedSig), Buffer.from(expected));
}

// In an Express handler:
app.post('/webhooks/tybrite', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['x-tybrite-signature'] as string;
  if (!verifyTybriteWebhook(req.body.toString(), sig, process.env.TYBRITE_WEBHOOK_SECRET!)) {
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(req.body.toString());
  switch (event.type) {
    case 'order.paid':
      await syncToErp(event.data.object);
      break;
    case 'payment.failed':
      await notifyOps(event.data.object);
      break;
  }

  res.json({ received: true });
});
Always read the raw request body (before JSON parsing) for signature verification. Parsers may reformat the JSON, producing a different byte sequence and causing verification to fail.

Endpoint Management

createWebhookEndpoint 🔒

Register a new HTTPS endpoint to receive event notifications.
const { webhook_endpoint } = await client.webhooks.createWebhookEndpoint({
  requestBody: {
    url: 'https://yourapp.com/webhooks/tybrite',
    events: ['order.paid', 'order.fulfilled', 'payment.succeeded', 'payment.failed'],
    enabled: true,
    metadata: { label: 'production-erp', team: 'platform' }
  }
});

// signing_secret is returned ONCE — store it immediately
const signingSecret = webhook_endpoint.signing_secret;
console.log('Store this secret:', signingSecret);
The signing_secret is shown once at creation. It cannot be retrieved again. Store it in your secrets manager (e.g. AWS Secrets Manager, Doppler, Infisical) immediately.
To subscribe to all events, pass ["*"] as the events array:
await client.webhooks.createWebhookEndpoint({
  requestBody: {
    url: 'https://yourapp.com/webhooks/tybrite',
    events: ['*'], // receive everything
  }
});
Supported event types:
CategoryEvents
Ordersorder.created, order.paid, order.fulfilled, order.cancelled, order.refunded, order.updated
Paymentspayment.succeeded, payment.failed, payment.refunded
Customerscustomer.created, customer.updated, customer.deleted
Inventoryproduct.created, product.updated, product.stock_low, product.out_of_stock
Cartcart.created, cart.updated, cart.abandoned
Gift Cardsgift_card.issued, gift_card.redeemed, gift_card.expired
Promotionspromotion.applied

listWebhookEndpoints 🔒

const { webhook_endpoints, pagination } = await client.webhooks.listWebhookEndpoints({
  limit: 20,
});

getWebhookEndpoint 🔒

Returns full endpoint details including delivery statistics.
const { webhook_endpoint } = await client.webhooks.getWebhookEndpoint({
  id: 'endpoint-uuid',
});

console.log(webhook_endpoint.delivery_stats);
// { total: 1420, successful: 1418, failed: 2 }

updateWebhookEndpoint 🔒

All fields are optional. Only provided fields are updated.
// Disable an endpoint temporarily
await client.webhooks.updateWebhookEndpoint({
  id: 'endpoint-uuid',
  requestBody: { enabled: false }
});

// Change subscribed events
await client.webhooks.updateWebhookEndpoint({
  id: 'endpoint-uuid',
  requestBody: {
    events: ['order.paid', 'order.cancelled', 'payment.failed']
  }
});

deleteWebhookEndpoint 🔒

Soft-deletes the endpoint. In-flight deliveries are not cancelled.
await client.webhooks.deleteWebhookEndpoint({ id: 'endpoint-uuid' });

sendTestWebhookEvent 🔒

Sends a synthetic test payload to an endpoint. Use this to verify your signature verification code and endpoint connectivity before going live. The payload includes "test": true so your handler can distinguish test from live events.
const result = await client.webhooks.sendTestWebhookEvent({
  id: 'endpoint-uuid',
  requestBody: { event_type: 'order.paid' }
});

console.log(result.success);     // true
console.log(result.status_code); // 200
Available test event types: order.created, order.paid, order.fulfilled, order.cancelled, payment.succeeded, payment.failed, customer.created, product.created, product.stock_low, cart.abandoned, gift_card.issued, promotion.applied.

Event Log

listWebhookEvents 🔒

Returns the event log in reverse-chronological order. Useful for debugging delivery failures or auditing event history.
// All events
const { webhook_events } = await client.webhooks.listWebhookEvents({ limit: 50 });

// Filter by type
const { webhook_events: failedPayments } = await client.webhooks.listWebhookEvents({
  type: 'payment.failed',
  limit: 20,
});

getWebhookEvent 🔒

Returns a single event with all its delivery attempts across all endpoints.
const { webhook_event } = await client.webhooks.getWebhookEvent({
  id: 'evt_1716199800000_abc123'
});

// Inspect delivery attempts
for (const delivery of webhook_event.deliveries) {
  console.log(delivery.attempt_number, delivery.success, delivery.status_code, delivery.latency_ms);
}

retryWebhookEvent 🔒

Manually re-delivers the event to all enabled endpoints subscribed to its event type. Each re-delivery is recorded as a new attempt.
const result = await client.webhooks.retryWebhookEvent({
  id: 'evt_1716199800000_abc123'
});

console.log(`Retried to ${result.retried} endpoint(s)`);
for (const r of result.results) {
  console.log(r.endpoint_id, r.success, r.status_code);
}

Event Payload Shape

All events use a consistent envelope:
{
  "id": "evt_1716199800000_abc123",
  "type": "order.paid",
  "created_at": "2026-05-20T10:30:00Z",
  "store_id": "store-uuid",
  "api_version": "v1",
  "data": {
    "object": { /* the resource — order, payment, customer, etc. */ }
  },
  "previous_attributes": {
    /* on update events: prior values for changed fields only */
  }
}
Use event.id for idempotency — store it and skip processing if you’ve seen it before:
const event = JSON.parse(req.body.toString());

// Idempotency check
if (await db.webhookEvents.exists(event.id)) {
  return res.json({ received: true }); // already processed
}

await db.webhookEvents.markSeen(event.id);
await processEvent(event);

Response Codes

CodeMeaning
200Success on reads, updates, deletes, retry, and test delivery.
201New endpoint created.
400Invalid URL (must be HTTPS), empty event list, invalid event type, or disabled endpoint on test.
401Missing or invalid API key.
403Publishable key used — all webhook operations require a secret key.
404Endpoint or event not found.
429Rate limit exceeded (500 req/hr per secret key).
500Internal server error.