A Real Account of Time Wasted
A while back, I needed to take on an async task for long-text summarization. In the first version, I used the most naive approach: the client runs a loop, calling the "check result" endpoint every 3 seconds, and errors out after 30 timeouts. Writing the code took 20 minutes, and it looked fine. The real time sink came afterward:
- The task takes an average of 40 seconds; with a 3-second polling interval, a single successful run means a dozen or so wasted poll requests;
- When a task failed, the loop logic was written sloppily and occasionally hung—I spent over an hour debugging it;
- Once user traffic picked up, the polling requests flooded the API logs so badly they became unreadable, and troubleshooting one production issue cost another 40 minutes.
Just like that, an entire afternoon was gone. Later I switched to a webhook callback approach—the same functionality, less than 30 lines of core code, written and running in just 15 minutes. This article walks through this "async task + callback" pattern end to end, using ThisToken.AI's API as the example. You can copy the code and start running it right away.
Why Webhooks Save Time Over Polling
Polling is essentially "I don't know if you're done, so I'll keep asking." Its cost structure is:
- Wasted requests: The task itself only needs 1 call, but polling generates 10–20 query requests;
- Latency locked to the interval: Even if the task finishes in 2 seconds, you still won't know until the next polling cycle;
- Complex failure paths: Loop, timeout, and backoff logic all have to be hand-written—get it wrong, and it's an incident.
Webhooks invert the model: you tell the service provider "call me when it's done," and then you go about your business. One task, one callback, no idle spinning in between. For indie developers, the most tangible benefit is—you no longer have to write the polling loop and its timeout branches; code you don't write is bugs you don't hit.
Step 1: Register and Get Your API Key
- Go to https://api.thistoken.ai/register and sign up with your email (the process is quick—just email verification);
- After logging in, go to the console, click "Create" on the "API Keys" page, and give the key a name (e.g.,
webhook-demo); - Copy the key and store it safely. It's only shown in full once at creation—if you lose it, you'll have to regenerate it.
For the key's usage quotas and billing details, refer to the official pricing page; don't trust any numbers relayed by third parties.
Step 2: Prepare a Callable URL
Webhooks require a publicly reachable HTTPS URL to receive callbacks. During local development, you don't need to buy a server—just use a tunneling tool, for example:
# Using a common tunneling tool; local server runs on port 8000
ngrok http 8000It will give you a temporary URL like https://xxxx.ngrok-free.app. Write it down—your callback URL will be https://xxxx.ngrok-free.app/webhook.
Step 3: Get Your First Code Running
The following Python code does three things: starts an HTTP server to receive callbacks, submits an async task, and extracts the result from the callback.
import time
import hashlib
import hmac
import threading
import requests
from flask import Flask, request, jsonify
API_KEY = "sk-你的APIKey"
BASE_URL = "https://api.thistoken.ai/v1"
WEBHOOK_URL = "https://xxxx.ngrok-free.app/webhook" # 换成你的穿透地址
SECRET = "my-webhook-secret" # 用于校验回调签名
app = Flask(__name__)
result_box = {}
@app.route("/webhook", methods=["POST"])
def webhook():
# 1. 校验签名,防止伪造回调
raw = request.get_data()
received = request.headers.get("X-Signature", "")
expected = hmac.new(
SECRET.encode(), raw, hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected, received):
return jsonify({"error": "invalid signature"}), 401
# 2. 取出任务结果
payload = request.get_json()
task_id = payload.get("task_id")
result_box[task_id] = payload.get("result")
print(f"收到回调: task_id={task_id}, status={payload.get('status')}")
return jsonify({"received": True}), 200
def submit_task():
"""提交异步任务,并登记回调地址"""
resp = requests.post(
f"{BASE_URL}/tasks",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"type": "summarize",
"input": "这里放一段需要摘要的长文本……",
"callback_url": WEBHOOK_URL,
"callback_secret": SECRET,
},
timeout=30,
)
resp.raise_for_status()
return resp.json()["task_id"]
if __name__ == "__main__":
task_id = submit_task()
print(f"任务已提交: {task_id},等待回调……")
# 本地起服务接收回调(生产环境请放到独立进程/机器上)
threading.Thread(
target=lambda: app.run(port=8000),
daemon=True,
).start()
for _ in range(60):
if task_id in result_box:
print("任务结果:", result_box[task_id])
break
time.sleep(1)
else:
print("超时未收到回调,请检查穿透地址是否可达")Run it, and normally you'll see the callback result in your terminal after ten-odd seconds. Pay attention to a few details:
- Signature verification is a must. Anyone who gets your callback URL can send you fake results;
HMACverification blocks forged requests; - Callbacks should return 200 quickly. Keep the callback handling logic light—persist to a database or enqueue first, and do time-consuming processing in the background, otherwise you may trigger the provider's retries;
- The callback endpoint must be idempotent. Providers typically retry unacknowledged callbacks, so the same
task_idmay arrive multiple times—deduplicate by task_id.
An Efficiency Comparison: Running the Numbers
Putting the two approaches side by side:
| Aspect | Polling Approach | Webhook Approach |
|---|---|---|
| Core code volume | Loop + timeout + backoff, ~80 lines | Submit + callback handling, ~30 lines |
| Network requests per task | 1 submission + 10–20 queries | 1 submission + 1 callback |
| Result notification latency | Limited by polling interval | Delivered immediately upon completion |
| Failure troubleshooting | Logs flooded by polling | One callback, clear chain of events |
My personal experience: the same async functionality went from "an afternoon to write and debug" to "running in 15 minutes, with time left for coffee." Based on the rough estimates above, for a small service running a few thousand tasks per day, switching to callbacks can cut query request volume by over 90%—and what you save isn't just server costs, but also the time you spend staring at logs.
Wrapping Up
Async tasks and callbacks are the standard pattern for integrating many AI capabilities. Once you get it working the first time, you'll find the pattern is universal. If this article helped you, go to https://api.thistoken.ai/register and sign up for an account, then run the code above—15 minutes later, your first callback will arrive.
---
Ready to try it yourself? Sign up at https://api.thistoken.ai/register to get your API key and start building.
Ready to try Token.AI?
Create a project-level API Key, enable channels in the console, and configure routing, budgets, and audit logs.
注册 ThisToken.AI 并获取 API Key