Skip to main content

Documentation Index

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

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

Webhooks let Sellfern notify your external systems in real time when events happen — new orders, status changes, fulfillment updates, expenses, and more. Instead of polling the API, you register an HTTPS endpoint and Sellfern delivers a signed POST request the moment an event occurs.

Available events

Sellfern emits the following events. You can subscribe to any combination when registering a webhook endpoint.
EventDescription
order.createdA new order has been created in Sellfern
order.updatedOne or more fields on an order have been updated
order.status_changedAn order’s status has transitioned (e.g. New → Shipped)
order.fulfillment.shippedA fulfillment for an order has been marked as shipped
order.fulfillment.deliveredA fulfillment has been confirmed as delivered
expense.createdA new expense record has been added
ticket.createdA new support ticket has been opened
ticket.updatedAn existing ticket has been updated

Creating a webhook endpoint

Via the dashboard

In Sellfern, go to Settings → Webhooks and click New endpoint. Paste your HTTPS URL, select the events you want to receive, and save. Sellfern generates an endpoint secret you can use to verify incoming payloads.

Via the API

Send a POST request to /api/v2/webhooks with a url and an events array. Your token must have the webhooks:manage scope.
curl -X POST "https://api.sellfern.com/api/v2/webhooks" \
  -H "Authorization: Bearer sk_live_YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/webhooks/sellfern",
    "events": ["order.created", "order.status_changed"]
  }'
A successful response returns HTTP 201 with the new endpoint object, including an id and the generated secret you should store securely.
All webhook management endpoints require a token with the webhooks:manage scope. Create or rotate tokens in Settings → API Tokens.

Webhook payload structure

Sellfern sends a POST request to your endpoint with a JSON body. The request includes two headers:
  • X-Sellfern-Event — the event name (e.g. order.status_changed)
  • X-Sellfern-Signature — an HMAC-SHA256 hex digest of the raw request body, signed with your endpoint secret
Here is an example payload for order.status_changed:
{
  "event": "order.status_changed",
  "order_id": 10482,
  "old_status": "In Production",
  "new_status": "Shipped",
  "tracking_number": "1Z999AA10123456784",
  "carrier": "UPS",
  "occurred_at": "2026-05-22T09:14:32Z"
}
Verifying the signature Compute the HMAC-SHA256 of the raw request body using your endpoint secret and compare it to X-Sellfern-Signature. Always use a constant-time comparison to prevent timing attacks:
const crypto = require('crypto');

function verifySignature(req, secret) {
  const signature = req.headers['x-sellfern-signature'];
  const expected = crypto
    .createHmac('sha256', secret)
    .update(req.rawBody)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature, 'hex'),
    Buffer.from(expected, 'hex')
  );
}

Managing webhook endpoints

MethodPathDescription
GET/api/v2/webhooksList all registered endpoints for your organization
PATCH/api/v2/webhooks/:idUpdate the URL, events, or enabled state of an endpoint
DELETE/api/v2/webhooks/:idDelete an endpoint (pass ?confirm=<id> in the query string)
POST/api/v2/webhooks/:id/testSend a test event to the endpoint
GET/api/v2/webhooks/:id/deliveriesRetrieve recent delivery attempts and their responses

Example: Discord order tracking bot

Sellfern ships a ready-to-deploy Express server example that listens for order.status_changed events and posts a formatted embed to a Discord channel. Here is the core handler pattern:
app.post('/webhook', async (req, res) => {
  // 1. Reject payloads with an invalid signature
  if (!verifySignature(req)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const event = req.headers['x-sellfern-event'];

  // 2. Only act on order.status_changed; silently acknowledge everything else
  if (event !== 'order.status_changed') {
    return res.status(200).json({ received: true, handled: false });
  }

  // 3. Build a Discord embed from the payload and post it
  const embed = buildEmbed(req.body);
  await postToDiscord(embed);
  return res.status(200).json({ received: true, handled: true });
});
The bot maps each order status to a Discord embed colour (grey for New, blue for In Production, green for Shipped, red for Cancelled) and includes tracking number and carrier when present. The example ships with a README covering local development with ngrok and deployment to Render or Railway.

Troubleshooting

Check that your URL is publicly reachable over HTTPS. Sellfern will not deliver to localhost or self-signed certificates. Use a tunnelling tool like ngrok during development and ensure the endpoint returns a 2xx status code within the timeout window.
Sellfern expects your endpoint to respond within 10 seconds. If your handler does heavy work, acknowledge the webhook immediately with a 200 response and process the payload asynchronously (for example, push it to a queue).
If your endpoint returns a non-2xx status or times out, Sellfern retries delivery with exponential backoff. You can inspect recent delivery attempts and their response codes in the dashboard under Settings → Webhooks → Deliveries, or via GET /api/v2/webhooks/:id/deliveries.
Make sure you are computing the HMAC over the raw request body before any JSON parsing. Many frameworks parse the body before your handler runs — you must capture the raw bytes separately (see the verify option in Express’s express.json() middleware).