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_id": "evt_...", "event_type": "tensorlake.sandbox.failed", "event_version": 1, "occurred_at": "...", "project_id": "...", "source_id": "...", "source_revision": "81247", "source_ordinal": 0, "data": { ... } }. 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.running or tensorlake.application.request.completed events?
  • 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_WEBHOOK_SECRET. 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:

EventEmitted When
tensorlake.sandbox.createdA sandbox is persisted for the first time
tensorlake.sandbox.runningA sandbox enters the running state
tensorlake.sandbox.suspendedA sandbox enters the suspended state
tensorlake.sandbox.resumedA suspended sandbox begins a new generation (status starts as pending)
tensorlake.sandbox.terminatedA sandbox terminates without a failure outcome
tensorlake.sandbox.failedA sandbox terminates with a failure outcome
tensorlake.application.createdAn application is persisted for the first time
tensorlake.application.updatedA deployment, metadata, capability or state change is persisted
tensorlake.application.deletedAn existing application is deleted
tensorlake.application.request.createdAn application request is created
tensorlake.application.request.completedAn application request completes
tensorlake.application.request.failedAn application request fails (data.request.failure_reason)

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


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 13, 2026

View on GitHub →