Shorty API

Webhooks — Shorty API

Receive signed Standard Webhooks events when Shorty jobs finish, instead of polling. Event catalog, signature verification (SDK and raw Node crypto), retry schedule, failure ladder, and secret rotation.

Open in ClaudeOpen in ChatGPT

Instead of polling, register an HTTPS endpoint and Shorty POSTs a signed event to it when a job finishes. Deliveries follow the Standard Webhooks specification, so you can verify them with any Standard-Webhooks library or the verifyWebhookSignature helper exported by @aishorty/sdk.

Event catalog

Every event data is the SAME shape you get from GET /v1/jobs/{id}, so one parser works for both polling and webhooks.

Event typeWhen it fires
job.succeededA job finished successfully. The payload data is the same shape as GET /v1/jobs/{id}.
job.failedA job terminated with an error. The payload data is the same shape as GET /v1/jobs/{id}.

job.succeeded payload

{
  "type": "job.succeeded",
  "timestamp": "2026-07-20T12:00:00.000Z",
  "data": {
    "jobId": "j1b2c3d4-0000-4000-8000-000000000000",
    "status": "success",
    "progress": 100,
    "step": null,
    "error": null,
    "output": {
      "jobType": "YOUTUBE_ARTICLE",
      "articleId": "a1b2c3d4-…"
    }
  }
}

job.failed payload

{
  "type": "job.failed",
  "timestamp": "2026-07-20T12:00:00.000Z",
  "data": {
    "jobId": "j1b2c3d4-0000-4000-8000-000000000000",
    "status": "error",
    "progress": 40,
    "step": null,
    "error": "Transcription failed: unreadable audio",
    "output": {}
  }
}

Verifying signatures

Each delivery carries three headers: webhook-id, webhook-timestamp, and webhook-signature. The signature is v1, + base64 HMAC-SHA256 over ${webhook-id}.${webhook-timestamp}.${rawBody}, keyed by the base64-decoded bytes of your endpoint secret (the part after whsec_).

Raw-body rule: verify the EXACT bytes you received. Parsing then re-serializing the JSON (JSON.stringify(JSON.parse(body))) reorders keys and WILL fail verification. Capture the raw request body before any JSON parsing.

With @aishorty/sdk

import { verifyWebhookSignature } from '@aishorty/sdk'

// rawBody is the exact string received, before JSON.parse.
const result = verifyWebhookSignature({
    payload: rawBody,
    headers: req.headers,
    secret: process.env.SHORTY_WEBHOOK_SECRET, // whsec_...
})
if (!result.valid) {
    return res.status(400).send(result.reason)
}
const event = JSON.parse(rawBody)

With Node crypto (no dependency)

import { createHmac, timingSafeEqual } from 'node:crypto'

function verify(rawBody, headers, secret) {
    const id = headers['webhook-id']
    const timestamp = headers['webhook-timestamp']
    const signatureHeader = headers['webhook-signature']
    const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64')
    const signed = `${id}.${timestamp}.${rawBody}`
    const expected = createHmac('sha256', key).update(signed, 'utf8').digest('base64')
    // Header may hold several space-separated "v1,<sig>" tokens (rotation).
    return signatureHeader.split(' ').some(token => {
        const [version, sig] = token.split(',')
        if (version !== 'v1' || !sig) return false
        const a = Buffer.from(sig, 'base64')
        const b = Buffer.from(expected, 'base64')
        return a.length === b.length && timingSafeEqual(a, b)
    })
}

Timestamps are rejected outside a ±5-minute tolerance (replay defense). Deliveries are sent with the Shorty-Webhooks/1.0 user agent and a 10-second per-attempt timeout.

Retries

A non-2xx response (or a connection error) is retried with exponential backoff and jitter: 9 attempts total (the initial attempt plus 8 retries) spread over roughly 45 hours (30s → 2m → 10m → 30m → 2h → 6h → 12h → 24h). Return any 2xx to acknowledge; a delivery that never succeeds is marked exhausted.

Failure ladder

We email the endpoint owner as consecutive failures mount — at 5, 10, 15 failures in a row — and AUTO-DISABLE the endpoint at 20 consecutive failures. A single successful delivery resets the counter to zero. Re-enable a disabled endpoint from Settings.

Secret rotation

Rotating an endpoint's secret opens a 24-hour dual-signing window: deliveries are signed with BOTH the new and previous secret (space-separated tokens in webhook-signature), so you can roll the secret on your side without dropping events. Accept a delivery if ANY token verifies.

Delivery security

  • Endpoints must be HTTPS. Requests to private, loopback, or link-local addresses are blocked (SSRF protection).
  • Only response snippets are stored for the delivery log — never full response bodies.
  • Manage endpoints, view the delivery log, send a test event, and rotate secrets in Settings.