Setup & Configuration
Create webhook endpoints, verify signatures, inspect deliveries, and replay failed events.
Create an endpoint
- Open Settings → Webhooks (opens the Cybership dashboard in a new tab) and choose Create Webhook.
- Enter your public HTTPS Endpoint URL and a Contact Email for failure notices.
- Under Event Subscriptions, select All Events or individual topics such as
orders/created. - Save the signing secret securely. It is shown once. Each endpoint has its own secret.
The request body is the topic's JSON payload. There is no additional type or data envelope. Read the event type from x-cybr-event-type and consult the topic reference for its fields.
For example, an order payload contains the order fields directly:
{
"id": "ord_example",
"order_number": "#1001",
"fulfillment_status": "UNFULFILLED"
}Request headers
| Header | Meaning |
|---|---|
x-cybr-event-id | Stable event ID. Use it to deduplicate retries and manual replays for an endpoint. |
x-cybr-event-type | Topic, such as orders/created. |
x-cybr-timestamp | Unix seconds when the event was created; unchanged across retries. |
x-cybr-api-version | Payload API version, currently v1. |
x-cybr-delivery-attempt | Attempt number for this delivery, starting at 1. |
x-cybr-idempotency-key | Stable key for this event and endpoint. Retries and manual replays send the same key. |
X-Cybr-Signature | Signature in the endpoint's existing format. Header names are case insensitive. |
Content-Type | application/json. |
User-Agent | Cybership-Webhooks/v25.9.1, unless the endpoint configures its own User-Agent header. |
Your configured custom headers accompany the request. Do not require a particular HTTP header order or capitalization.
Deliveries are at least once. A receiver may accept a request just before its response is lost, so the same event can arrive again. Save the event ID and accepted work durably before returning 2xx.
Verify signatures
Endpoints use HMAC-SHA256 with a hexadecimal signature in the form t=timestamp,v1=hash. The signed bytes are the signature timestamp, a comma, and the request body. Use t from the signature for freshness checks, not x-cybr-timestamp.
Endpoints created before October 2026 keep their existing secret and signature format, including configured SHA256/SHA512 and hex/base64 encoding, so their verifiers keep working without a secret replacement. The example below verifies the default SHA256/hex format over the raw request bytes.
Register the raw-body route before any JSON body parser. Parsing and serializing the body can change escapes, key order, or large numbers and invalidate the signature.
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verifyWebhookSignature(rawBody, signature, secret, now = Math.floor(Date.now() / 1000)) {
if (!Buffer.isBuffer(rawBody) || typeof signature !== 'string' || signature.length > 8192) return false;
if (typeof secret !== 'string' || !secret || !Number.isSafeInteger(now)) return false;
const parts = signature.split(',');
const timestamps = parts.filter(part => part.startsWith('t=')).map(part => part.slice(2));
const hashes = parts.filter(part => part.startsWith('v1=')).map(part => part.slice(3));
if (timestamps.length !== 1 || !/^[0-9]{1,16}$/.test(timestamps[0])) return false;
const timestamp = Number(timestamps[0]);
if (!Number.isSafeInteger(timestamp) || Math.abs(now - timestamp) > 300) return false;
const expected = createHmac('sha256', secret)
.update(timestamps[0] + ',')
.update(rawBody)
.digest();
return hashes.some(hash => /^[0-9a-f]{64}$/i.test(hash) &&
timingSafeEqual(expected, Buffer.from(hash, 'hex')));
}A receiver can then validate and store the event before acknowledging it:
app.post('/webhooks', express.raw({ type: 'application/json', limit: '1mb' }), async (req, res) => {
if (!verifyWebhookSignature(req.body, req.headers['x-cybr-signature'], process.env.WEBHOOK_SECRET)) {
return res.status(400).send('Invalid signature');
}
try {
const payload = JSON.parse(req.body.toString('utf8'));
const eventId = req.get('x-cybr-event-id');
const eventType = req.get('x-cybr-event-type');
if (!eventId || !eventType) return res.status(400).send('Missing event headers');
// Implement this with a durable, deduplicated inbox in your application.
await saveAcceptedWebhook({ eventId, eventType, payload });
return res.sendStatus(204);
} catch {
return res.sendStatus(503);
}
});Process longer tasks from that inbox. Validate the selected topic's payload before acting on it. Keep payloads, secrets, and authorization headers out of application logs.
Delivery and recovery
- Return
2xxpromptly. Delivery allows 5 seconds to connect, 10 seconds to receive response headers after connecting, and 15 seconds total. DNS lookup is included in the total budget. - Transport failures,
408,429, and retryable5xxresponses are retried. Redirects, other4xx,501, and505are permanent failures. - Each delivery allows at most 8 attempts over 72 hours. Backoff starts between 2.5 and 5 seconds, doubles between attempts, and is capped at 1 hour, with jitter. A valid
Retry-Aftercan delay the next attempt further, within the deadline. - By default, 10 consecutive qualifying endpoint failures open the circuit. Delivery pauses, and an email is sent when a contact address is configured. Cybership worker failures do not count against your endpoint.
- After a one-hour cooldown, one delivery probes the endpoint. Success resumes delivery; failure starts another cooldown. Choose Activate Webhook to try the next waiting delivery straight away. A deactivated endpoint stays off until you activate it, and events that happen while it is off are not sent to it.
Delivery sends one request at a time per endpoint, at least 100 milliseconds apart. A retry waits for its backoff while newer events continue, so do not rely on arrival order. These are ceilings, not throughput guarantees; response time and shared worker capacity also affect delivery.
Test, inspect, and replay
Choose Test Webhook to send the built-in test event to an active endpoint. For local development, use a public HTTPS tunnel; private, loopback, link-local, and metadata destinations are blocked. Redirects are not followed.
Open View Logs to inspect deliveries. Select a delivery to page through its full attempt history with Older attempts, and choose Details on an attempt. Each attempt can show request headers and payload, response headers and body, status, timing, and the recorded failure. Attempts sent before October 2026 show only their status and time. Redacted, truncated, and expired details are marked explicitly.
Use Replay failed deliveries to preview and confirm a bounded batch. The result identifies queued and skipped deliveries. Successful or already-pending deliveries are not silently re-sent. Replay retains the event ID and records the requesting operator. Fix the receiver first and continue deduplicating at your endpoint.
Request payloads and request/response inspection details are normally available for 7 days; delivery and attempt metadata for 30 days. Expired payloads cannot be replayed.
Replace a secret
Secrets remain stable until you choose Replace Secret. Replacement pauses the endpoint and immediately replaces its key; events that happen before you activate it again are not sent to it. Save the new secret, update your receiver, then choose Activate Webhook. There is no automatic rotation schedule or overlap window.
Viewing logs and attempt details needs the Read Webhooks permission. Changing endpoints, testing, replacing secrets and replaying deliveries need Write Webhooks; deleting an endpoint needs Delete Webhooks.
Manage endpoints with the API
You can also create and manage endpoints with the REST API, using an API key in the x-access-token header. Each request applies only to the endpoints of the API key's team. An endpoint ID from another team returns 404.
| Request | What it does | Permission |
|---|---|---|
GET /shipos/webhooks | Lists endpoints. Use page (from 0) and limit (default 20, at most 50); meta.has_next_page tells you whether another page exists. Filter with status=active,paused,inactive. | Read Webhooks |
GET /shipos/webhooks/{webhook_id} | Returns one endpoint with its status, health, and last delivery. | Read Webhooks |
POST /shipos/webhooks | Creates an endpoint and returns its signing secret. | Write Webhooks |
PATCH /shipos/webhooks/{webhook_id} | Changes the description, topics, custom headers, or contact email. The URL can't be changed. | Write Webhooks |
POST /shipos/webhooks/{webhook_id}/deactivate | Pauses deliveries. | Write Webhooks |
POST /shipos/webhooks/{webhook_id}/activate | Resumes deliveries, or requests a recovery attempt after repeated failures stopped them. | Write Webhooks |
POST /shipos/webhooks/{webhook_id}/rotate-secret | Replaces the signing secret and pauses the endpoint. | Write Webhooks |
POST /shipos/webhooks/{webhook_id}/test | Queues the built-in test/webhook event for an active endpoint. | Write Webhooks |
DELETE /shipos/webhooks/{webhook_id} | Deletes the endpoint and discards its secret and custom headers. | Delete Webhooks |
Owner and Admin API keys have every webhook permission. A Member API key needs a role with the Webhooks permissions.
Keep these rules in mind:
- The secret is shown once. Only the create and rotate-secret responses include the signing secret. Other responses never include it or your custom header values, so store the secret when you receive it.
- Replacing a secret pauses deliveries. After
rotate-secret, the old secret stops working and nothing is sent until you update your receiver and callactivate. Every rotate-secret call creates another secret, so don't retry one that succeeded. - Each URL is registered once per team. Repeating a create request for the same URL returns a
400validation error instead of creating a second endpoint with a different secret. A team can have at most 50 endpoints. - Activate and deactivate are safe to repeat. Deleting an endpoint that was already deleted returns
404. - Test only active endpoints. Testing a paused or inactive endpoint returns
400. A successful test returns202once the event is queued, not when your receiver accepts it.
Errors use problem details with the application/problem+json content type, and the usual rate limits apply. Delivery logs and replay are available in Settings → Webhooks (opens the Cybership dashboard in a new tab), not through the API.