What is a Callback in API Design? Understanding Async Patterns

Understand callbacks in API design with practical examples of webhook-style URLs, SSE, and WebSocket patterns so you can handle long-running, async operations efficiently while keeping your applications responsive and secure.

TRY NANO BANANA FOR FREE

What is a Callback in API Design? Understanding Async Patterns

TRY NANO BANANA FOR FREE
Contents

APIs must handle operations that take time. A database query, file processing, or external service call might take seconds or minutes. Callbacks provide a way to handle these operations efficiently.

What is a Callback in API Context?

In API design, a callback is a mechanism where the client provides a URL or function for the server to invoke when an operation completes. Instead of the client waiting and polling, the server pushes the result.

The term "callback" comes from the pattern: the server calls back to the client when finished.

Think of ordering food at a restaurant. You place your order (make a request). Instead of waiting at the counter, they give you a number (task ID). When your food is ready, they call your number (callback). This is more efficient than standing at the counter waiting.

Types of Callback Patterns

Different API styles use different callback mechanisms.

Webhook callbacks use HTTP POST requests. The server sends the result to a URL the client provides:

// Client initiates
POST /api/orders
{ "product": "dog-food", "callback_url": "https://client.com/callbacks/order-status" }

// Server responds immediately
{ "order_id": "12345", "status": "processing" }

// Later, server calls back
POST https://client.com/callbacks/order-status
{ "order_id": "12345", "status": "completed" }

This is the most common pattern for modern APIs.

Server-Sent Events (SSE) maintain a connection and stream updates:

const eventSource = new EventSource('/api/orders/stream');

eventSource.addEventListener('order-update', (event) => {
  const update = JSON.parse(event.data);
  handleOrderUpdate(update);
});

The server keeps the connection open and sends events as things change.

WebSocket callbacks use bidirectional connections for callback-style communication:

const socket = io('https://api.example.com');

socket.emit('process', { data: 'large dataset' });

socket.on('complete', (result) => {
  handleResult(result);
});

The Evolution of Async APIs

Early web APIs were entirely synchronous. Clients made requests and waited for responses. This was simple but didn't scale well.

Synchronous requests:

Client: "Process this order"
Server: "OK, processing... (5 seconds) here's your result"
Client: "Thanks"

This blocking approach wastes connections. Each waiting client ties up server resources.

Polling was the first solution:

Client: "Any orders yet?"
Server: "No"
Client: "Any orders yet?"
Server: "No"
Client: "Any orders yet?"
Server: "Here's your result"

Better, but still wasteful. The client must constantly ask.

Callbacks revolutionized the pattern:

Client: "Process this order, call me back at this URL when done"
Server: "OK, I'll call you back"
[Server processes in background]
Server: POST to callback URL: "Here's your result"

The most efficient pattern. No waiting, no constant asking.

When Callbacks Make Sense

Use callbacks for operations that take more than a few seconds:

Data processing:

// Generate a report
POST /api/reports
{ "type": "sales", "callback_url": "https://app.com/callbacks/reports" }

File operations:

// Upload and process an image
POST /api/images/process
{ "image_url": "...", "callback_url": "https://app.com/callbacks/image" }

External integrations:

// Process payment through provider
POST /api/payments
{ "amount": 100, "provider": "stripe", "callback_url": "https://app.com/callbacks/payment" }

The server handles the long work and notifies you when done.

Implementing Callback Handlers

Your application needs endpoints to receive callbacks:

const express = require('express');
const crypto = require('crypto');
const app = express();

app.use(express.json());

// Verify requests come from trusted sources
function verifyCallback(req, res, next) {
  const signature = req.headers['x-callback-signature'];
  const payload = JSON.stringify(req.body);

  const expected = crypto
    .createHmac('sha256', process.env.CALLBACK_SECRET)
    .update(payload)
    .digest('hex');

  if (signature !== `sha256=${expected}`) {
    return res.status(401).send('Invalid signature');
  }

  next();
}

// Callback endpoint
app.post('/callbacks/order-status', verifyCallback, (req, res) => {
  const { order_id, status, result } = req.body;

  if (status === 'completed') {
    // Update order in database
    updateOrder(order_id, { status: 'completed', result });

    // Notify user
    sendNotification(order_id, 'Your order is ready!');
  } else if (status === 'failed') {
    updateOrder(order_id, { status: 'failed', error: result.error });
    sendNotification(order_id, 'Order processing failed');
  }

  res.status(200).send('OK');
});

Always verify callback signatures. This prevents attackers from sending fake callbacks.

Callback vs Webhook

People often confuse callbacks and webhooks. They serve similar purposes but differ in who initiates.

Callback:

  • Client initiates by providing their URL
  • Server uses client's URL to respond
  • One-time or recurring, client controls

Webhook:

  • Server initiates to notify client
  • Client must register URL in advance
  • Used for event notifications, usually recurring

The terms are often used interchangeably. Technically, the client "registers" for webhooks while providing a "callback URL" for callbacks.

Common Callback Patterns

Status callback: Notify when status changes

{
  "task_id": "task_123",
  "status": "completed",
  "result": { "summary": "..." }
}

Progress callback: Notify during long operations

{
  "task_id": "task_123",
  "progress": 50,
  "message": "Processing items 500-1000"
}

Error callback: Notify when things go wrong

{
  "task_id": "task_123",
  "status": "failed",
  "error": {
    "code": "PROCESSING_ERROR",
    "message": "Failed to process image"
  }
}

Security Considerations

Always verify signatures:

app.post('/callbacks/webhook', (req, res) => {
  const signature = req.headers['x-api-signature'];
  if (!isValidSignature(req.body, signature)) {
    return res.status(401).send('Invalid');
  }
  // Process
});

Use HTTPS:

app.post('/callbacks/webhook', (req, res) => {
  // Your callback endpoint must be HTTPS in production
});

Implement idempotency:

const processedCallbacks = new Set();

app.post('/callbacks/webhook', (req, res) => {
  const { callback_id } = req.body;

  if (processedCallbacks.has(callback_id)) {
    return res.status(200).send('Already processed');
  }

  processedCallbacks.add(callback_id);
  // Process...
});

Pet Store API and Callbacks

The Pet Store API supports callbacks for async operations. When you initiate a long-running operation, provide a callback URL to receive results.

Check docs.petstoreapi.com for supported operations and callback formats. Set up your handler following the patterns above.

Callbacks transform slow operations from blocking to background. Your application stays responsive while the server does the heavy work.