Completion Webhooks
AsyncQueue can notify your application when a task finishes by sending an HTTP POST to a URL you specify at task creation.
Setting up a completion webhook
Section titled “Setting up a completion webhook”Add the onCompleteUrl field when creating a task:
curl -X POST https://api.asyncqueue.io/v1/tasks \ -H "Authorization: Bearer your-api-key" \ -H "Content-Type: application/json" \ -d '{ "targetUrl": "https://api.example.com/process-order", "onCompleteUrl": "https://api.example.com/task-finished" }'Webhook payload
Section titled “Webhook payload”When the task reaches a terminal status (completed, failed, or timeout), AsyncQueue sends a POST request to your onCompleteUrl:
{ "event": "task.completed", "task": { "id": "550e8400-e29b-41d4-a716-446655440000", "status": "completed", "result": { "statusCode": 200, "headers": {"content-type": "application/json"}, "body": "{\"success\": true}", "duration": 1234 }, "error": null, "retryCount": 0, "createdAt": "2024-01-15T10:30:00.000Z", "completedAt": "2024-01-15T10:30:02.234Z" }}Webhook behavior
Section titled “Webhook behavior”- Webhooks are fire-and-forget with a 10-second timeout
- Failed webhooks do not affect the task status
- Webhooks are not retried on failure
Webhook signatures
Section titled “Webhook signatures”All callback requests and completion webhooks include an HMAC-SHA256 signature in the X-AsyncQueue-Signature header. Use this to verify that requests are genuinely from AsyncQueue.
X-AsyncQueue-Signature: t=1705312260,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bdVerifying signatures
Section titled “Verifying signatures”The signature is computed as HMAC-SHA256(webhook_secret, "{timestamp}.{request_body}"). Here is how to verify it:
import { createHmac, timingSafeEqual } from 'crypto';
function verifyWebhook(rawBody, signatureHeader, webhookSecret) { const [tPart, vPart] = signatureHeader.split(','); const timestamp = tPart.split('=')[1]; const receivedSig = vPart.split('=')[1];
const computed = createHmac('sha256', webhookSecret) .update(`${timestamp}.${rawBody}`) .digest('hex');
return timingSafeEqual(Buffer.from(computed), Buffer.from(receivedSig));}
// In your webhook handlerapp.post('/webhook', (req, res) => { const signature = req.headers['x-asyncqueue-signature']; if (!verifyWebhook(req.rawBody, signature, process.env.WEBHOOK_SECRET)) { return res.status(401).send('Invalid signature'); } // Process the webhook...});Managing your webhook secret
Section titled “Managing your webhook secret”Each team gets a webhook secret (prefixed whsec_) on creation. You can view and rotate it from the dashboard or via the API:
# View your webhook secretcurl https://api.asyncqueue.io/teams/{teamId}/webhook-secret \ -H "Authorization: Bearer your-jwt-token"
# Rotate to a new secret (invalidates the old one immediately)curl -X POST https://api.asyncqueue.io/teams/{teamId}/webhook-secret/rotate \ -H "Authorization: Bearer your-jwt-token"Polling as a fallback
Section titled “Polling as a fallback”Since completion webhooks are best-effort, implement polling as a fallback for critical workflows:
const { task } = await createTask({ targetUrl: 'https://api.example.com/process', onCompleteUrl: 'https://api.example.com/notify'});
// Poll as fallbackconst checkStatus = async () => { const res = await fetch(`https://api.asyncqueue.io/v1/tasks/${task.id}`, { headers: { 'Authorization': `Bearer ${API_KEY}` } }); const data = await res.json(); if (['completed', 'failed', 'timeout'].includes(data.task.status)) { return data.task; } return null;};