Docs / Getting started

Quickstart

Create a project, schedule a delayed job and a nightly cron schedule, and receive a signed webhook, in roughly ten minutes.

Last updated March 4, 2026

#Before you start

You need three things: a Tend account, an HTTPS endpoint that can receive a POST request, and a terminal. If you do not yet have a public endpoint, a tunneling tool pointed at a local server works fine for development. Tend will not deliver to plain http:// URLs in production projects, and it will not follow redirects, so give it the final address.

Create a project in the dashboard at tendcomputer.com. Every project comes with a development key beginning tnd_dev_ and a production key beginning tnd_live_. This guide uses the development key throughout, so nothing you do here can touch production data.

Shell
# Keep the key out of your shell history and source files
export TEND_API_KEY="tnd_dev_8fQ2mLx0aVt3"

# Install one SDK (all optional; curl works everywhere)
pip install tend
npm install @tend/sdk
go get github.com/tendcomputer/tend-go

#Create a delayed job

A delayed job runs once. You provide a target URL, a payload, and either an absolute run_at timestamp or a relative delay_seconds. The example below asks Tend to call your endpoint 60 seconds from now with a small JSON payload. The Idempotency-Key header makes the request safe to retry: if your network drops after we accept the job, sending the same key and body within 24 hours returns the original job instead of creating a duplicate.

cURL
curl -X POST https://api.tendcomputer.com/v2/jobs \
  -H "Authorization: Bearer $TEND_API_KEY" \
  -H "Idempotency-Key: welcome-email-user-4821" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://app.example-shop.dev/hooks/send-welcome",
    "delay_seconds": 60,
    "payload": { "user_id": 4821, "template": "welcome_v3" },
    "retry": { "max_attempts": 5 }
  }'

A successful call returns 201 Created with the stored job. Note the status of scheduled and the computed next_run_at.

JSON
{
  "id": "job_01J8Q4ZC7N3XWD5B9E2KHRT6VA",
  "status": "scheduled",
  "url": "https://app.example-shop.dev/hooks/send-welcome",
  "region": "us-east",
  "payload": { "user_id": 4821, "template": "welcome_v3" },
  "retry": { "max_attempts": 5, "base_seconds": 10, "cap_seconds": 3600 },
  "next_run_at": "2026-03-04T15:22:41Z",
  "created_at": "2026-03-04T15:21:41Z"
}

#Receive the result

When the job fires, Tend sends a POST to your URL. Your handler should verify the Tend-Signature header, do the work, and return a 2xx status within 15 seconds. That timeout is the webhook timeout: a slower response counts as a failed attempt and the run is retried. If the work takes longer, accept the request, respond 202, and finish in the background.

Signature verification is one line in each SDK. The signing secret for your project is shown once in the dashboard under Webhooks, and always begins with whsec_.

Python
from flask import Flask, request
from tend import Tend, SignatureError

app = Flask(__name__)

@app.post("/hooks/send-welcome")
def send_welcome():
    try:
        event = Tend.webhooks.verify(
            payload=request.get_data(),
            signature=request.headers["Tend-Signature"],
            secret="whsec_9d1f3c7a2b",
        )
    except SignatureError:
        return "bad signature", 400

    user_id = event.payload["user_id"]
    # ...send the email, keyed on event.run_id to stay idempotent...
    return "", 204

#Add a cron schedule

A schedule is a recurring job definition. It needs a unique name, a cron expression, an IANA timezone, and the same url and payload fields a job takes. The schedule below runs a report at 02:00 every day in Denver time. Cron expressions use five fields (minute, hour, day of month, month, day of week), and daylight-saving transitions are handled by the timezone, not by you.

cURL
curl -X POST https://api.tendcomputer.com/v2/schedules \
  -H "Authorization: Bearer $TEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "nightly-usage-report",
    "cron": "0 2 * * *",
    "timezone": "America/Denver",
    "url": "https://app.example-shop.dev/hooks/usage-report",
    "payload": { "window": "24h" }
  }'

If the expression is malformed or the timezone is not a real IANA zone, you receive 400 invalid_cron_expression. If it would fire more often than every 30 seconds, you receive 422 interval_too_short. A second schedule with the same name in the same environment returns 409 schedule_name_taken.

#Verify and clean up

Confirm that your job ran by fetching its runs. A healthy result has one run in the succeeded state and one attempt.

Shell
curl "https://api.tendcomputer.com/v2/jobs/job_01J8Q4ZC7N3XWD5B9E2KHRT6VA/runs?limit=10" \
  -H "Authorization: Bearer $TEND_API_KEY"
JSON
{
  "data": [
    {
      "id": "run_01J8Q52M0HB8T4YQF7C3ZPX9DE",
      "job_id": "job_01J8Q4ZC7N3XWD5B9E2KHRT6VA",
      "status": "succeeded",
      "attempts": 1,
      "started_at": "2026-03-04T15:22:41Z",
      "finished_at": "2026-03-04T15:22:42Z",
      "response_status": 204
    }
  ],
  "next_cursor": null
}

To remove the sample schedule, delete it by ID. Deleting a schedule stops future runs but leaves existing run history in place until it ages out of your plan's log retention window.

Shell
curl -X DELETE https://api.tendcomputer.com/v2/schedules/sch_01J8Q5BN2VK6M1RHW3D0AYTC8F \
  -H "Authorization: Bearer $TEND_API_KEY"