Skip to content

Completion Webhooks

AsyncQueue can notify your application when a task finishes by sending an HTTP POST to a URL you specify at task creation.

Add the onCompleteUrl field when creating a task:

Terminal window
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"
}'

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"
}
}
  • Webhooks are fire-and-forget with a 10-second timeout
  • Failed webhooks do not affect the task status
  • Webhooks are not retried on failure

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=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd

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 handler
app.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...
});

Each team gets a webhook secret (prefixed whsec_) on creation. You can view and rotate it from the dashboard or via the API:

Terminal window
# View your webhook secret
curl 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"

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 fallback
const 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;
};