Agent skill
Tensorlake Webhooks Skill
Receive and verify Tensorlake webhooks (sandbox, application and application-request lifecycle events, signed and delivered by Svix). Use when setting up Tensorlake webhook handlers, debugging svix-signature verification, or handling events like tensorlake.sandbox.failed, tensorlake.sandbox.running and tensorlake.application.request.completed.
Install this skill
npx skills add hookdeck/webhook-skills --skill tensorlake-webhooks
Tensorlake signs every webhook delivery with Svix, following Standard Webhooks with the svix-* header names. Each delivery is an HTTP POST with a signed JSON body and three headers: svix-id, svix-timestamp, and svix-signature. Webhooks are scoped to one project and report sandbox, application and application-request lifecycle changes. The body is a versioned envelope: { "event_. event_id is stable across retries (the deduplication key). Order related events from the same source_id by source_revision (a numeric string) and then source_ordinal.
When to Use This Skill
- How do I receive Tensorlake webhooks?
- How do I verify Tensorlake webhook signatures (
svix-signature)? - How do I handle
tensorlake.sandbox.failed,tensorlake.sandbox.runningortensorlake.application.request.completedevents? - How do I react to a failed sandbox or a failed application request?
- Why is my Tensorlake webhook signature verification failing, or rejecting Hookdeck retries?
Verification (core)
Signed content is {svix-id}.{svix-timestamp}.{raw body}. Svix signs it with HMAC-SHA256 using the base64-decoded secret (the part after whsec_), base64-encodes the result, and sends one or more space-separated v1,<signature> entries. Use the official svix library, which compares in constant time. Read the svix-* headers, and when svix-id is absent fall back to the webhook-* headers (webhook-id, webhook-timestamp, webhook-signature), as Hookdeck does.
import { Webhook } from 'svix'; // svix 2.x is ESM-only
const prefix = req.headers['svix-id'] ? 'svix' : 'webhook';
const id = req.headers[`${prefix}-id`];
const timestamp = req.headers[`${prefix}-timestamp`];
const signature = req.headers[`${prefix}-signature`];
// rawBody: the raw Buffer/string, never re-serialized JSON
new Webhook(process.env.TENSORLAKE_WEBHOOK_SECRET).verify(rawBody, {
'svix-id': id, 'svix-timestamp': timestamp, 'svix-signature': signature,
'webhook-id': id, 'webhook-timestamp': timestamp, 'webhook-signature': signature,
}); // throws on a bad signature or a timestamp outside the window
const event = JSON.parse(rawBody.toString('utf8')); // parse only after verify passes
from svix.webhooks import Webhook, WebhookVerificationError
wh = Webhook(os.environ["TENSORLAKE_WEBHOOK_SECRET"]) # "whsec_..."
wh.verify(raw_body, {"svix-id": id, "svix-timestamp": ts, "svix-signature": sig,
"webhook-id": id, "webhook-timestamp": ts, "webhook-signature": sig})
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Each destination has its own signing secret: open the project's Webhooks page in Tensorlake Cloud, open the destination, and copy the value under Signing secret. It begins with whsec_. Keep the full value in TENSORLAKE_. Handlers fail closed: with the variable unset (or not a whsec_ value) they return 500 instead of accepting anything. Tensorlake points at Svix's libraries and manual procedure; the svix library used here reads either header set, so no mapping is needed beyond the fallback shown.
Hookdeck retries and replays hit the 5-minute window
The svix and standardwebhooks libraries always enforce a fixed 5-minute timestamp window, and it can't be configured. Hookdeck retries and replays resend the original svix-timestamp, so the library rejects any retry or replay that happens more than 5 minutes after the original delivery. If you verify behind Hookdeck (or replay old events), use the manual verifier in references/verification.md, where the window can be dropped or widened. Deduplicate on event_id instead of relying on the timestamp.
Common Event Types
12 events are documented (the list may not be exhaustive). The name is event_type in the JSON body, and the examples handle all of them:
| Event | Emitted When |
|---|---|
tensorlake.sandbox.created | A sandbox is persisted for the first time |
tensorlake.sandbox.running | A sandbox enters the running state |
tensorlake.sandbox.suspended | A sandbox enters the suspended state |
tensorlake.sandbox.resumed | A suspended sandbox begins a new generation (status starts as pending) |
tensorlake.sandbox.terminated | A sandbox terminates without a failure outcome |
tensorlake.sandbox.failed | A sandbox terminates with a failure outcome |
tensorlake.application.created | An application is persisted for the first time |
tensorlake.application.updated | A deployment, metadata, capability or state change is persisted |
tensorlake.application.deleted | An existing application is deleted |
tensorlake.application.request.created | An application request is created |
tensorlake.application.request.completed | An application request completes |
tensorlake.application.request.failed | An application request fails (data.request.failure_) |
See references/overview.md for the full catalog and payload shapes.
Environment Variables
# Signing secret of the webhook endpoint (Svix format, starts with whsec_)
TENSORLAKE_WEBHOOK_SECRET=whsec_xxxxx
Local Development
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 tensorlake --path /webhooks/tensorlake
Register the tunnel URL as a destination on the project's Webhooks page in Tensorlake Cloud, then send a synthetic event with Test webhook on the destination's page.
Reference Materials
- references/overview.md - Tensorlake webhook concepts, events, payload shapes, delivery
- references/setup.md - Create a destination, get the secret, send a test event
- references/verification.md - Svix verification details, header fallback, manual verifier, timestamp window