Webhooks
Workspace webhooks send Bytes Link events to an HTTPS endpoint that you control.
They are designed for backend services, automation platforms and integration middleware. They are not browser notifications and should not be received directly by frontend code.
Workspace webhooks are available on Business and Enterprise plans. Workspace owners and administrators can manage them from:
Workspace Settings > Developer API > Webhooks
Supported events
The current event catalogue is:
| Event | When it is sent |
|---|---|
link.created | A tracked Link is created. |
link.updated | A Link destination or meaningful configuration changes. Notes-only changes do not trigger an event. |
link.disabled | A Link is disabled or archived. |
Each endpoint can subscribe to one or more supported events.
Endpoint requirements
Webhook destinations must:
- use HTTPS
- be reachable from the public internet
- avoid embedded credentials
- avoid sensitive query parameters
- pass Bytes destination safety validation
Redirects are not followed during delivery.
Signing secret
Each endpoint has its own signing secret. Bytes shows the full secret once when the endpoint is created or the secret is rotated.
Store it in a backend secrets manager or environment variable. Do not place it in browser code, source control, support tickets, chat messages or screenshots.
Request headers
Every delivery includes:
Content-Type: application/json
X-Bytes-Webhook-Id: <delivery public ID>
X-Bytes-Webhook-Timestamp: <Unix timestamp>
X-Bytes-Webhook-Signature: v1=<hex digest>
X-Bytes-Webhook-Schema: 1
X-Bytes-Webhook-Event: <event type>
User-Agent: Jawwws-Bytes-Webhook/1.0
The webhook ID identifies the delivery attempt group. The canonical event ID is included in the JSON body.
Signature validation
Bytes signs the exact request body with HMAC-SHA256.
Build the signed message as:
<timestamp>.<raw request body>
Then calculate:
v1=HMAC_SHA256(signing_secret, signed_message)
Compare the calculated signature with X-Bytes-Webhook-Signature using a constant-time comparison.
Do not parse and re-serialise the JSON before validating it. Validate the raw request body exactly as received.
Also reject timestamps that fall outside the tolerance your integration accepts, such as five minutes.
Node.js example
import crypto from "node:crypto";
export function verifyBytesWebhook({ rawBody, timestamp, signature, secret }) {
const signedMessage = `${timestamp}.${rawBody}`;
const expected = `v1=${crypto
.createHmac("sha256", secret)
.update(signedMessage, "utf8")
.digest("hex")}`;
const providedBuffer = Buffer.from(signature, "utf8");
const expectedBuffer = Buffer.from(expected, "utf8");
return (
providedBuffer.length === expectedBuffer.length &&
crypto.timingSafeEqual(providedBuffer, expectedBuffer)
);
}
Payload contract
A link.created delivery follows this shape:
{
"id": "01K0EVENTPUBLICID0000000000",
"event": "link.created",
"schema_version": 1,
"created_at": "2026-07-28T16:40:00+00:00",
"workspace": {
"id": "cops-coffee",
"name": "Cops & Coffee"
},
"data": {
"link": {
"id": "01K0LINKPUBLICID00000000000",
"slug": "summer-coffee",
"short_url": "https://jaww.ws/summer-coffee",
"destination_url": "https://example.com/coffee",
"title": "Summer coffee menu",
"status": "active",
"is_active": true,
"expires_at": null,
"is_password_protected": false,
"channel": "print",
"utm_source": "counter-card",
"utm_medium": "qr",
"utm_campaign": "summer-coffee",
"utm_term": null,
"utm_content": "card-v1",
"created_at": "2026-07-28T16:40:00+00:00",
"updated_at": "2026-07-28T16:40:00+00:00"
},
"changes": {}
}
}
link.updated includes safe changed fields under data.changes.
link.disabled includes the reason disabled or archived.
Webhook payloads do not include notes, password hashes, plaintext passwords, API keys, signing secrets, authentication cookies, internal numeric IDs or click-level personal data.
Test deliveries
Use Send test in Workspace Settings to queue a synthetic webhook.test event through the same signing, destination validation, queue and delivery audit path as live events.
A test payload contains endpoint and workspace identifiers only. It does not contain customer Link data.
Retries
Retryable failures include connection failures, HTTP 408, 425, 429 and server responses from 500 upwards.
Bytes attempts delivery up to five times with bounded backoff. A terminal failure moves the endpoint to Needs attention. Reactivate the endpoint after correcting the receiving service or destination.
Delivery history
The app shows the latest 100 deliveries per endpoint, including:
- event and delivery public IDs
- trigger type
- delivery status
- attempt count
- HTTP status
- response duration
- safe error code and summary
- timestamps
Bytes never stores webhook response bodies, request signatures or signing secrets in delivery history.
Manual replay
A delivery can be replayed from its history while the endpoint is active.
Replay creates a new signed delivery from the original immutable event payload. It does not alter the original delivery or its attempt history.
Receiver behaviour
A receiver should:
- Read and retain the raw request body.
- Check the timestamp tolerance.
- Verify the HMAC signature with a constant-time comparison.
- Record the event ID for idempotency.
- Return a
2xxresponse quickly. - Process longer work asynchronously.
Treat repeated event IDs as safe duplicates rather than applying the same business action twice.