Go from zero to a verified human judgment in under 5 minutes. This guide walks you through creating your first HumanRail task.
Sign up at humanrail.dev and create an organization. Navigate to Settings → API Keys and generate a new key.
API keys are prefixed with ek_live_ for production and ek_test_ for
sandbox. Use the sandbox key during development -- it simulates worker responses
without hitting the real worker pool.
Set the key as an environment variable:
export HUMANRAIL_API_KEY="ek_test_your_key_here"
Choose your language and install the HumanRail client library.
npm install @humanrail/sdk
pip install humanrail
go get github.com/humanrail/humanrail-go
A task is a unit of work that gets routed to a vetted human worker. You define the input payload, the expected output schema, and a payout amount. HumanRail handles routing, verification, and payment.
import { HumanRail } from "@humanrail/sdk";
const client = new HumanRail({
apiKey: process.env.HUMANRAIL_API_KEY,
});
const task = await client.tasks.create({
idempotencyKey: "order-12345-refund-check",
taskType: "refund_eligibility",
riskTier: "medium",
slaSeconds: 300,
payload: {
orderId: "order-12345",
orderTotal: 89.99,
reason: "Item arrived damaged",
},
outputSchema: {
type: "object",
required: ["eligible", "reason_code"],
properties: {
eligible: { type: "boolean" },
reason_code: {
type: "string",
enum: ["approved", "denied_policy", "denied_timeframe"],
},
notes: { type: "string" },
},
},
payout: { currency: "USD", maxAmount: 0.50 },
callbackUrl: "https://myapp.com/webhooks/humanrail",
});
console.log(task.id, task.status);
// "01abc123-..." "posted"
import os
from humanrail import HumanRail
client = HumanRail(api_key=os.environ["HUMANRAIL_API_KEY"])
task = client.tasks.create(
idempotency_key="order-12345-refund-check",
task_type="refund_eligibility",
risk_tier="medium",
sla_seconds=300,
payload={
"orderId": "order-12345",
"orderTotal": 89.99,
"reason": "Item arrived damaged",
},
output_schema={
"type": "object",
"required": ["eligible", "reason_code"],
"properties": {
"eligible": {"type": "boolean"},
"reason_code": {
"type": "string",
"enum": ["approved", "denied_policy", "denied_timeframe"],
},
"notes": {"type": "string"},
},
},
payout={"currency": "USD", "maxAmount": 0.50},
callback_url="https://myapp.com/webhooks/humanrail",
)
print(task.id, task.status)
# "01abc123-..." "posted"
package main
import (
"context"
"fmt"
"os"
"github.com/humanrail/humanrail-go"
)
func main() {
client := humanrail.NewClient(os.Getenv("HUMANRAIL_API_KEY"))
task, err := client.Tasks.Create(context.Background(), &humanrail.TaskCreateParams{
IdempotencyKey: "order-12345-refund-check",
TaskType: "refund_eligibility",
RiskTier: humanrail.RiskTierMedium,
SLASeconds: 300,
Payload: map[string]any{
"orderId": "order-12345",
"orderTotal": 89.99,
"reason": "Item arrived damaged",
},
OutputSchema: map[string]any{
"type": "object",
"required": []string{"eligible", "reason_code"},
"properties": map[string]any{
"eligible": map[string]any{"type": "boolean"},
"reason_code": map[string]any{
"type": "string",
"enum": []string{"approved", "denied_policy", "denied_timeframe"},
},
"notes": map[string]any{"type": "string"},
},
},
Payout: &humanrail.Payout{Currency: "USD", MaxAmount: 0.50},
CallbackURL: "https://myapp.com/webhooks/humanrail",
})
if err != nil {
panic(err)
}
fmt.Println(task.ID, task.Status)
// "01abc123-..." "posted"
}
You have two options for receiving the verified result: webhooks (recommended) or polling.
Set a callbackUrl when creating the task. HumanRail will send a
task.verified event when the worker's response passes verification.
See the Webhooks guide for signature verification and retry details.
import express from "express";
import { verifyWebhookSignature } from "@humanrail/sdk";
const app = express();
app.post("/webhooks/humanrail", express.raw({ type: "application/json" }), (req, res) => {
const signature = req.headers["x-escalation-signature"];
const isValid = verifyWebhookSignature({
payload: req.body,
signature,
secret: process.env.HUMANRAIL_WEBHOOK_SECRET,
});
if (!isValid) return res.status(401).send("Invalid signature");
const event = JSON.parse(req.body);
if (event.type === "task.verified") {
console.log("Verified output:", event.data.output);
// { eligible: true, reason_code: "approved", notes: "..." }
}
res.sendStatus(200);
});
from fastapi import FastAPI, Request, HTTPException
from humanrail import verify_webhook_signature
app = FastAPI()
@app.post("/webhooks/humanrail")
async def handle_webhook(request: Request):
body = await request.body()
signature = request.headers.get("x-escalation-signature")
if not verify_webhook_signature(
payload=body,
signature=signature,
secret=os.environ["HUMANRAIL_WEBHOOK_SECRET"],
):
raise HTTPException(status_code=401, detail="Invalid signature")
event = request.json()
if event["type"] == "task.verified":
print("Verified output:", event["data"]["output"])
# {"eligible": True, "reason_code": "approved", "notes": "..."}
return {"ok": True}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
signature := r.Header.Get("X-Escalation-Signature")
if !humanrail.VerifyWebhookSignature(body, signature, webhookSecret) {
http.Error(w, "Invalid signature", http.StatusUnauthorized)
return
}
var event humanrail.WebhookEvent
json.Unmarshal(body, &event)
if event.Type == "task.verified" {
fmt.Println("Verified output:", event.Data.Output)
}
w.WriteHeader(http.StatusOK)
}
If you prefer not to set up webhooks, you can poll the task status until it
reaches a terminal state (verified, failed,
expired, or cancelled).
const waitForResult = async (taskId: string) => {
while (true) {
const task = await client.tasks.get(taskId);
if (task.status === "verified") {
return task.output;
}
if (["failed", "expired", "cancelled"].includes(task.status)) {
throw new Error(`Task ended with status: ${task.status}`);
}
await new Promise((r) => setTimeout(r, 5000)); // poll every 5s
}
};
const output = await waitForResult(task.id);
console.log(output);
// { eligible: true, reason_code: "approved", notes: "..." }
Webhooks are strongly recommended for production. Polling works for development
and simple scripts, but it wastes resources and adds latency. Use the
task.verified webhook event for real-time results.
Before going live, make sure you have covered the following:
ek_test_ to ek_live_ API keyPOST /tasks calls to prevent duplicatesverified, failed, expired, cancelledtask.failed and task.expired eventsPOST /webhooks/test endpoint to verify your handlerX-Request-Id header from responses for debugging
Never trigger business-critical actions based solely on task.submitted.
Always wait for task.verified -- this is the event that confirms
the worker's output has passed HumanRail's verification pipeline.