Xatern OneAPI v1

Webhooks

A webhook is a call Xatern One makes to your server when something changes. Use it to keep your own systems in step without asking again and again.

Make endpoints in Settings, Developers, or with the webhook endpoints. A company can have up to 10.

Events

TypeWhen it is sent
order.createdA new order is placed.
order.paidpayment_status changes to paid.
order.shippedstatus changes to shipped.
order.completedstatus changes to completed.
order.cancelledstatus changes to cancelled.
order.updatedAny other change to status or payment_status.
product.updatedA catalog product or size is saved.
pingYou press Send test, or call the test endpoint.

Body

JSON
{
  "id": "evt_0123456789abcdef01234567",
  "type": "order.paid",
  "created": "2026-11-02T08:20:00Z",
  "company": "5d7b8a0e-2c43-4c2f-9d41-7a1e0c3f6b92",
  "data": {
    "order": { "number": "NT-482913", "status": "confirmed", "payment_status": "paid", "...": "full order object" }
  }
}

For product.updated, data.product is {id, name, status, skus}. For ping, data is {}. The order object is described in Orders.

Headers

HeaderValue
Content-Typeapplication/json
User-AgentXaternOne-Webhooks/1
Xatern-EventThe event type, for example order.paid.
Xatern-DeliveryThe delivery id. Same on every retry of one delivery.
Xatern-Signaturet=<unix seconds>,v1=<hex>

Answer fast

Reply with any 2xx status within 10 seconds. Do the slow work after you reply. Xatern One does not follow redirects. A redirect counts as a failure.

The same event can arrive more than once. Use the event id to ignore repeats.

Check the signature

Each endpoint has a signing secret that starts with whsec_. The signature is an HMAC-SHA256, in hex, of the text t.body: the timestamp, a full stop, then the raw request body. The key is the signing secret. Always use the raw body, not a re-encoded copy. Reject a call if the signature does not match, or if t is more than 5 minutes old.

Node

JavaScript
import crypto from "node:crypto";

export function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map(p => p.split("=")));
  const t = parts.t, v1 = parts.v1;
  if (!t || !v1) return false;
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
  const expected = crypto.createHmac("sha256", secret)
    .update(t + "." + rawBody).digest("hex");
  const a = Buffer.from(expected), b = Buffer.from(v1);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

PHP

PHP
<?php
function verify(string $rawBody, string $header, string $secret): bool {
    $parts = [];
    foreach (explode(',', $header) as $p) {
        [$k, $v] = array_pad(explode('=', $p, 2), 2, '');
        $parts[$k] = $v;
    }
    if (empty($parts['t']) || empty($parts['v1'])) return false;
    if (abs(time() - (int)$parts['t']) > 300) return false;
    $expected = hash_hmac('sha256', $parts['t'] . '.' . $rawBody, $secret);
    return hash_equals($expected, $parts['v1']);
}

$raw = file_get_contents('php://input');
$ok = verify($raw, $_SERVER['HTTP_XATERN_SIGNATURE'] ?? '', getenv('XATERN_WEBHOOK_SECRET'));
http_response_code($ok ? 200 : 400);

Python

Python
import hashlib, hmac, time

def verify(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
    t, v1 = parts.get("t"), parts.get("v1")
    if not t or not v1:
        return False
    if abs(time.time() - int(t)) > 300:
        return False
    signed = t.encode() + b"." + raw_body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, v1)

Retries

Any answer that is not 2xx, a timeout or a connection error is a failed attempt. Xatern One tries again after these waits, for 7 attempts in all:

AttemptSent
1Straight away
21 minute after attempt 1
35 minutes after attempt 2
430 minutes after attempt 3
52 hours after attempt 4
66 hours after attempt 5
712 hours after attempt 6

After attempt 7 the delivery is marked failed. You can send it again from the delivery log or with the resend call.

Auto-disable

If an endpoint has 15 failed deliveries in a row and none has worked for 3 days, Xatern One turns it off. The endpoint shows disabled_at and disabled_reason. Fix your server, then set enabled to true again.

Roll the secret

If a secret leaks, use roll-secret. The old secret stops working at once. Events are kept for 30 days.

Xatern One API v1. Example data on these pages is made up.