Docs / Guides

Webhooks and signature verification

How Tend delivers job results to your endpoint, how to verify the Tend-Signature header, and how delivery retries behave over a 48-hour window.

Last updated April 2, 2026

#What webhook deliveries are

When a run reaches a final state, Tend can send an HTTP POST to a URL you control describing the outcome. This is separate from the request Tend makes to run your job: the job target does the work, and the webhook tells your system what happened. Typical uses are updating a status column, notifying a user, or triggering a follow-up job.

Webhook endpoints are configured per project with POST /v2/webhook-endpoints. Each endpoint has a URL, a set of subscribed event types, and its own signing secret, shown once at creation time. Store it the way you would store a database password.

Event typeSent when
run.succeededA run completes with a 2xx response
run.failedA run exhausts its retries or hits a permanent 4xx failure
run.retryingAn attempt fails and another is scheduled
schedule.pausedA schedule is paused, manually or by the project owner

#Registering an endpoint

cURL
curl https://api.tendcomputer.com/v2/webhook-endpoints \
  -H "Authorization: Bearer tnd_live_8f2c1a9d4e7b" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://api.example-shop.dev/hooks/tend",
    "events": ["run.succeeded", "run.failed"]
  }'

#Anatomy of a delivery

Each delivery is a POST with a JSON body and a small set of headers. The Tend-Signature header carries a timestamp and one or more signatures in the form t=<unix_seconds>,v1=<hex_hmac>. The payload is the same event object you would get from GET /v2/events/{id}.

JSON
{
  "id": "evt_01JA1F6Z9M3S",
  "type": "run.failed",
  "created_at": "2026-04-02T09:17:44Z",
  "data": {
    "run_id": "run_01JA1F5VQ8WD",
    "job_id": "job_01JA1F5T2C6N",
    "status": "failed",
    "attempts": 6,
    "last_http_status": 502
  }
}
Text
POST /hooks/tend HTTP/1.1
Content-Type: application/json
Tend-Signature: t=1775121464,v1=5c1b0e6f3a9d2e47b8f01c6d7a3e94f2d0b58c1e6a7f3d29b4e8c0a1f5d6e7b2
Tend-Event-Id: evt_01JA1F6Z9M3S

#Verifying signatures

To verify a delivery, build the signed string by concatenating the timestamp, a literal period, and the raw request body, exactly as received. Compute an HMAC-SHA256 of that string using your endpoint's signing secret, hex-encode the result, and compare it to the v1 value using a constant-time comparison. Reject the delivery if the timestamp is more than five minutes from your server's clock; this blocks replays of captured requests.

Python
import hashlib
import hmac
import time

def verify_tend_signature(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    timestamp = parts["t"]
    if abs(time.time() - int(timestamp)) > 300:
        return False
    signed = timestamp.encode() + b"." + raw_body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])

During secret rotation, a delivery may carry two v1 values, one signed with the old secret and one with the new. Accept the delivery if either matches.

#Responding to deliveries

Your endpoint must respond with a 2xx status within 15 seconds. The response body is ignored. Anything else, including a timeout, a connection error, a redirect to a different host, or a non-2xx status, counts as a failed delivery and is scheduled for retry.

The practical pattern is to verify the signature, persist the event, return 200, and process it afterward. Doing slow work inline risks the 15-second limit and causes redundant deliveries.

#Delivery retries and the 48-hour window

Failed deliveries are retried with the same exponential-backoff-with-jitter scheme used for job runs, for up to 48 hours after the first attempt. After the window closes, the delivery is marked abandoned and stays visible in the dashboard for the length of your plan's log retention. You can replay an abandoned delivery manually.

cURL
curl -X POST https://api.tendcomputer.com/v2/events/evt_01JA1F6Z9M3S/redeliver \
  -H "Authorization: Bearer tnd_live_8f2c1a9d4e7b"

Because deliveries are retried, the same event can reach you more than once. Deduplicate on the id field of the event body (or the Tend-Event-Id header) and make your handler idempotent.