logo

When your task depends on an external system, you cannot control response speed. A payment processor might take 2 seconds or 30 seconds. A file converter might need 5 minutes. A human approver might need 3 days. Without proper timeout configuration, your tasks either fail prematurely or hang with no resolution.

This guide covers how to set timeouts at each level and what to do when they fire.

Step 1: Understand the Two Types of Timeouts

AsyncQueue provides two distinct timeout mechanisms:

1. HTTP Timeout (timeout) - How long to wait for your target URL to respond to the HTTP request. If the server does not reply within this window, the request fails and may be retried.

2. Wait Timeout (maxWaitTime) - How long a task can remain in the waiting state (for wait-for-signal tasks). If no signal arrives within this window, the task transitions to timeout status.

Task Created
├── HTTP Timeout (timeout: 30s)
│ Governs: How long the HTTP call to targetUrl can take
│ On expire: Request fails, retry if attempts remain
└── Wait Timeout (maxWaitTime: 3600s)
Governs: How long the task can sit in "waiting" state
On expire: Task moves to "timeout", onComplete fires

These mechanisms operate independently. A task can survive the HTTP timeout (callback succeeds) but later hit the wait timeout (no signal arrives).

Step 2: Set HTTP Timeouts for Callback Execution

The timeout field controls how long the HTTP request to your target URL can take before the system aborts the connection.

// Fast internal service - tight timeout
await aq.tasks.create({
targetUrl: 'https://your-app.com/api/quick-check',
timeout: 5, // 5 seconds
maxRetries: 3,
});
// Slow external API - generous timeout
await aq.tasks.create({
targetUrl: 'https://slow-api.example.com/process',
timeout: 120, // 2 minutes
maxRetries: 2,
});

What happens when the HTTP timeout fires:

  1. The HTTP connection is aborted
  2. The task counts this as a failed attempt
  3. If retries remain, the task is re-queued with exponential backoff
  4. If all retries are exhausted, the task moves to failed status

Tip: Set your timeout slightly higher than your external service’s expected response time. If the service typically responds in 10 seconds, use timeout: 15 to avoid false failures during slow periods.

Step 3: Set Wait Timeouts for Signal-Based Tasks

For tasks with waitForSignal: true, the maxWaitTime field controls how long the task can remain in the waiting state.

// Payment confirmation - wait up to 1 hour
const { task, signalToken } = await aq.tasks.create({
targetUrl: 'https://your-app.com/api/init-payment',
waitForSignal: true,
maxWaitTime: 3600, // 1 hour
timeout: 30, // HTTP timeout for the initial callback
});
// Document approval - wait up to 7 days
const { task, signalToken } = await aq.tasks.create({
targetUrl: 'https://your-app.com/api/request-approval',
waitForSignal: true,
maxWaitTime: 604800, // 7 days
timeout: 10,
});

What happens when the wait timeout fires:

  1. The task transitions from waiting to timeout
  2. The signal token is invalidated (cannot be used afterward)
  3. The onCompleteUrl webhook fires with the timeout status
  4. Your application can handle the timeout event

Step 4: Design Fallback Behavior for Timeouts

Timeouts are not errors - they are expected events. Design your application to handle them gracefully.

Pattern: onComplete webhook handles all terminal states

app.post('/api/task-webhook', async (req, res) => {
const { task } = req.body;
switch (task.status) {
case 'completed':
await fulfillOrder(task.id, task.result);
break;
case 'failed':
await notifySupport('Task failed', task.id, task.error);
break;
case 'timeout':
// The external dependency did not respond in time
await cancelPendingOrder(task.id);
await notifyCustomer(task.id, 'Your request timed out. Please try again.');
break;
}
res.json({ received: true });
});

Pattern: escalation on timeout

For critical workflows, escalate rather than fail silently:

case 'timeout':
// Create a new task with longer timeout and alert the team
await aq.tasks.create({
targetUrl: 'https://your-app.com/api/manual-review',
payload: { originalTaskId: task.id, reason: 'timeout' },
});
await slackNotify('#ops', `Task ${task.id} timed out, escalated to manual review`);
break;

Step 5: Choose Timeout Values for Common Scenarios

Use CaseHTTP TimeoutWait TimeoutNotes
Internal API call5-10sN/AYour own services should respond fast
Payment processing30s3600s (1h)Stripe/PayPal callbacks arrive fast, but customer action varies
File conversion120s1800s (30m)Video/PDF processing time varies by file size
Email sending10sN/AEmail APIs respond fast; delivery happens asynchronously
Human approval10s604800s (7d)Initial request resolves fast; human response takes longer
Third-party API30-60sN/AMatch the service’s documented timeout
Webhook relay10s86400s (24d)Accept fast, wait for external callback

General rules:

  • Set HTTP timeout to 2-3x the expected response time
  • Set wait timeout to the maximum acceptable delay for your business logic
  • Handle the timeout event in every workflow - never assume tasks will complete