Author picture Phil Leggetter

Hookdeck vs Supabase Queues: Choosing the Right Serverless Queue

Published


When you need serverless queuing for background jobs or event delivery, two solutions emerge: Supabase Queues and Hookdeck Event Gateway. Choosing between them depends on whether you're handling internal jobs or external events.

Both handle asynchronous work, but they come from very different philosophies: one built for webhooks and event delivery, the other for database-backed job queues. This post walks through those differences to help you decide which fits your use case.

TL;DR

Supabase Queues is a Postgres-native queue built on pgmq, ideal for background tasks that live close to your database. Hookdeck Event Gateway is a managed event gateway designed for reliable webhook ingestion and delivery. Choose Supabase Queues for internal job processing. Choose Hookdeck Event Gateway when handling external webhooks that need buffering, retries, and delivery guarantees.

Understanding the Models

Both Supabase Queues and Hookdeck Event Gateway handle asynchronous work, but they shine in different scenarios. Let's explore how each works for internal job queuing and external webhook handling.

Scenario 1: Internal Job Queuing

For internal background jobs, like processing orders, sending emails, or generating reports, both solutions provide ways to queue and process work.

Supabase Queues for Internal Jobs

Supabase Queues is built directly on top of Postgres using the pgmq extension. Messages live in tables, and consumers explicitly pull jobs for processing.

Here's a typical workflow for processing background jobs with Supabase Queues:

Supabase Internal Queue Example
typescript
// Send a message to a queue
const { data, error } = await supabase.rpc('pgmq_send', {
  queue_name: 'order_processing',
  message: { orderId: 123, amount: 99.99 }
});

// Read messages from queue
const { data: messages } = await supabase.rpc('pgmq_read', {
  queue_name: 'order_processing',
  vt: 30,  // visibility timeout in seconds
  qty: 1   // number of messages to read
});

// Process and delete message
if (messages && messages.length > 0) {
  const msg = messages[0];
  await processOrder(msg.message);
  
  // Delete after successful processing
  await supabase.rpc('pgmq_delete', {
    queue_name: 'order_processing',
    msg_id: msg.msg_id
  });
}

Because it's inside your database, you can use SQL, triggers, and row-level security to integrate tightly with your app's data model.

Hookdeck Event Gateway for Internal Jobs

Hookdeck Event Gateway uses a publish model for internal events, automatically delivering them to configured destinations with built-in retries and rate limiting.

With Hookdeck Event Gateway, the same workflow uses push-based delivery:

Hookdeck Publish API Example
typescript
// Publish an internal event
await fetch('https://hkdk.events/v1/publish', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${HOOKDECK_API_KEY}`,
    'X-Hookdeck-Source-Name': 'order-processing'
  },
  body: JSON.stringify({
    event: 'order.created',
    data: { orderId: 123, amount: 99.99 }
  })
});

// Your destination endpoint receives the event automatically
// No polling required - push-based delivery

Hookdeck Event Gateway handles the delivery, retries, and observability automatically - you don't need to write consumer polling logic.

Scenario 2: External Webhook Handling

This is where the differences become most significant. Handling webhooks from third-party services like Stripe, Shopify, or GitHub involves unique challenges: signature verification, traffic spikes, replay requirements, and delivery guarantees.

Supabase Queues for External Webhooks

With Supabase, you need to build the complete webhook receiver and processor infrastructure yourself. This requires two Edge Functions plus CRON configuration.

Webhook Receiver Function:

Supabase Webhook Receiver Edge Function
typescript
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'

serve(async (req) => {
  const supabase = createClient(
    Deno.env.get('SUPABASE_URL')!,
    Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
  )

  // Verify webhook signature (e.g., Stripe)
  const signature = req.headers.get('stripe-signature')
  const body = await req.text()
  
  // You must implement signature verification for each provider
  // This is critical for security but complex to maintain
  const isValid = await verifyStripeSignature(signature, body)
  if (!isValid) {
    return new Response('Invalid signature', { status: 401 })
  }
  
  const event = JSON.parse(body)

  // Push to queue for processing
  const { data, error } = await supabase.rpc('pgmq_send', {
    queue_name: 'stripe_events',
    message: event
  })

  if (error) {
    return new Response(JSON.stringify({ error: error.message }), {
      status: 500,
      headers: { 'Content-Type': 'application/json' }
    })
  }

  return new Response(JSON.stringify({ received: true }), { status: 200 })
})

Queue Processor Function (triggered by CRON):

Supabase Queue Processor Edge Function
typescript
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'

serve(async (req) => {
  const supabase = createClient(
    Deno.env.get('SUPABASE_URL')!,
    Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
  )

  // Read messages from queue
  const { data: messages } = await supabase.rpc('pgmq_read', {
    queue_name: 'stripe_events',
    vt: 30,  // visibility timeout in seconds
    qty: 10  // batch size
  })

  for (const msg of messages || []) {
    try {
      // Process the Stripe event
      await processStripeEvent(msg.message)
      
      // Delete after successful processing
      await supabase.rpc('pgmq_delete', {
        queue_name: 'stripe_events',
        msg_id: msg.msg_id
      })
    } catch (error) {
      // Message becomes visible again after timeout
      // You need to implement retry logic and dead-letter handling
      console.error('Processing failed:', error)
    }
  }

  return new Response(
    JSON.stringify({ processed: messages?.length || 0 }),
    { status: 200 }
  )
})

async function processStripeEvent(event: any) {
  // Your business logic here
  switch (event.type) {
    case 'payment_intent.succeeded':
      // Handle successful payment
      break
    case 'customer.subscription.created':
      // Handle new subscription
      break
    // ... handle other event types
  }
}

You'll also need to configure a CRON job in Supabase to trigger the processor function regularly (e.g., every 10 seconds).

Key considerations with this approach:

This approach requires additional development work. You'll need manual signature verification for each webhook provider, and every provider implements it differently. Building retry logic with exponential backoff and managing CRON configuration all require custom code. While pgmq provides message archiving capabilities, you'll need to implement your own retry policies and error handling. You'll also have limited visibility into event processing status without building your own monitoring and alerting.

Benefits of this approach:

The upside is complete control over the webhook processing pipeline. You can customize retry logic to your exact needs, implement complex routing based on your database state, and keep webhook processing close to your data. For teams already invested in Supabase, this approach minimizes external dependencies and keeps your stack consolidated.

Hookdeck Event Gateway for External Webhooks

Hookdeck Event Gateway was built specifically for this use case. You provide Hookdeck Event Gateway's URL to the webhook provider, and it handles verification, buffering, retries, and delivery automatically.

Hookdeck Single Destination Function
typescript
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"

serve(async (req) => {
  // Verify Hookdeck signature
  const hookdeckSignature = req.headers.get('x-hookdeck-signature')
  const hookdeckSecret = Deno.env.get('HOOKDECK_WEBHOOK_SECRET')!
  
  const rawBody = await req.text()
  
  // Compute HMAC SHA-256 signature
  const encoder = new TextEncoder()
  const key = await crypto.subtle.importKey(
    'raw',
    encoder.encode(hookdeckSecret),
    { name: 'HMAC', hash: 'SHA-256' },
    false,
    ['sign']
  )
  
  const signature = await crypto.subtle.sign(
    'HMAC',
    key,
    encoder.encode(rawBody)
  )
  const computedSignature = btoa(
    String.fromCharCode(...new Uint8Array(signature))
  )
  
  if (computedSignature !== hookdeckSignature) {
    return new Response('Invalid signature', { status: 401 })
  }

  // Process event directly - no queue polling needed
  const event = JSON.parse(rawBody)
  await processStripeEvent(event)
  
  return new Response(JSON.stringify({ success: true }), { status: 200 })
})

async function processStripeEvent(event: any) {
  // Your business logic here
  switch (event.type) {
    case 'payment_intent.succeeded':
      // Handle successful payment
      break
    case 'customer.subscription.created':
      // Handle new subscription
      break
    // ... handle other event types
  }
}

What Hookdeck Event Gateway handles automatically:

Hookdeck Event Gateway eliminates the infrastructure work. Setup is straightforward: create a Source in Hookdeck's dashboard, provide the Source URL to your webhook provider (like Stripe), and configure your destination endpoint.

Once configured, Hookdeck automatically verifies signatures for popular webhook providers like Stripe, Shopify, and GitHub, plus supports generic HMAC verification for custom sources. When traffic spikes hit, events buffer automatically rather than overwhelming your destination.

The built-in retry system uses exponential backoff, and you can configure rate limits to protect downstream services. Every event appears in the observability dashboard with its full history, making debugging straightforward. If something fails, you can replay events with a single click. No CRON jobs, no polling logic, no infrastructure to manage.

Considerations with this approach:

Using Hookdeck Event Gateway introduces an external dependency and additional cost beyond your existing infrastructure. Events flow through Hookdeck's infrastructure before reaching your application, which adds a network hop (though this is typically negligible). For teams that need complete control over webhook processing logic or prefer to minimize external services, this managed approach may feel less flexible than building custom solutions.

Key Differences Summary

For Internal Job Queuing:

  • Both solutions work well
  • Supabase Queues: Ideal if you're already using Supabase and want database-native queuing
  • Hookdeck Event Gateway: Best for push-based delivery with built-in observability and no polling logic

For External Webhook Handling:

  • Supabase Queues: Requires 2 Edge Functions + CRON + manual signature verification for each provider
  • Hookdeck Event Gateway: Single function with automatic verification, buffering, retries, and observability

Hookdeck Event Gateway's value proposition is strongest for external webhooks - it eliminates the complexity of building and maintaining webhook infrastructure while providing enterprise-grade reliability and observability.

Comparing Hookdeck and Supabase Queues

The table below compares architectural differences, operational characteristics, and ideal use cases. While both handle asynchronous work, they optimize for different scenarios. Understanding these tradeoffs helps you choose the right tool.

DimensionSupabase QueuesHookdeck Event Gateway
Core modelPostgres-backed message queue using pgmqManaged event gateway and queueing infrastructure (Hookdeck Event Gateway)
Delivery typePull-based - consumers pop messages via SQL or APIPush-based - events are automatically delivered to destinations (HTTP, queues, or services)
Primary use caseInternal background jobs or workflows tightly coupled to your dataExternal webhook and event ingestion, buffering, and delivery
Retries and dead-letter handlingManual or custom implementationBuilt-in retries, visibility timeouts, error logging, and replay
Rate limiting and throughput controlLimited to database performance and consumer logicConfigurable delivery rates per destination to protect downstream services (control throughput guide)
ScalabilityBound by Postgres I/O, locks, and storage limitsHorizontally scalable, managed infrastructure built for high throughput
Observability and metricsBasic - inspect queue tables or build dashboards manuallyFull event-level visibility, queue depth, retries, and replay history in the Hookdeck Event Gateway Dashboard
Operational overheadLow, but requires tuning of Postgres performance and worker concurrencyZero infrastructure to manage - Hookdeck Event Gateway handles scaling, durability, and retries
Cost modelIncluded within Supabase database usageUsage-based pricing for managed event delivery (Hookdeck pricing)
Ecosystem integrationWorks best if your app already uses SupabaseCompatible with any HTTP event source or destination (AWS SQS, GCP Pub/Sub, RabbitMQ, etc.)
Best fitInternal job processing, background workers, transactional tasksReliable webhook/event delivery, buffering, and controlled throughput

The key architectural difference drives everything else: Supabase Queues uses a pull-based model where your code polls for work, while Hookdeck Event Gateway pushes events to your destinations automatically. This fundamental distinction affects scalability, operational complexity, and which use cases each system handles best.

Cost Considerations

Supabase Queues is included as part of your Supabase database tier, with no separate pricing for queue usage. The cost is determined by your overall database usage, compute, and storage requirements.

Hookdeck uses usage-based pricing with a free tier that includes 10,000 events per month. Paid plans start at $39/month and include metered billing, with event pricing scaling from $1.00 per 100,000 events down to as low as $0.33 per 100,000 events at higher volumes.

For detailed current pricing, see Hookdeck pricing and Supabase pricing.

Choosing Between Them

When to Use Supabase Queues

Supabase Queues is ideal for internal background job processing within your application. It makes the most sense when you're already using Supabase for your database and authentication, since queues live directly in Postgres alongside your data. This proximity shines for internal operations like sending emails, cleaning data, generating reports, or processing uploaded files.

The database-native approach means queue operations can participate in transactions with your application data. You get full control and visibility at the SQL level, which matters when you need to debug or tune performance. This works well at moderate scale with predictable traffic patterns, where Postgres performance characteristics are well understood.

When to Use Hookdeck Event Gateway

Hookdeck Event Gateway's primary use case is handling external webhooks from third-party APIs like Stripe, Shopify, GitHub, or any other webhook provider. It receives and verifies webhooks automatically, buffers events during traffic spikes to prevent data loss, and delivers them with retry guarantees and exponential backoff. The full observability dashboard lets you debug, trace, and replay event flows without digging through logs.

Beyond external webhooks, Hookdeck Event Gateway proves valuable for internal service-to-service communication in specific scenarios. When traffic volumes are high or unpredictable, it protects downstream services from overload. The push-based delivery model eliminates polling logic, and advanced features like filtering, transformations, and fan-out routing let you build sophisticated event architectures without managing infrastructure.

Hybrid Approach

Some architectures benefit from using both systems in tandem. The most common pattern uses Hookdeck Event Gateway as the front-line buffer for external webhooks, then forwards events into Supabase Queues for internal job processing.

This makes sense when you need Hookdeck Event Gateway's webhook-specific capabilities (signature verification, replay, observability) but want subsequent processing to happen in Supabase Queues where it can transact with your database. For example, a Stripe webhook might arrive through Hookdeck Event Gateway, trigger an immediate acknowledgment, then get queued in Supabase for database updates that need to happen atomically with other operations.

That said, most teams don't need this complexity. Choosing the right tool for each specific scenario (Hookdeck Event Gateway for external webhooks, Supabase Queues for internal jobs) keeps architecture simpler and more maintainable.

Choosing Your Approach

The decision ultimately comes down to your event sources and architectural needs:

Use Supabase Queues when:

  • You're processing internal background jobs within an existing Supabase application
  • Queue operations need to participate in database transactions
  • You prefer database-native queuing with minimal external dependencies
  • Traffic patterns are predictable and within Postgres performance limits

Use Hookdeck Event Gateway when:

  • You're handling external webhooks from third-party APIs
  • You need automatic signature verification for popular webhook providers
  • High-volume or unpredictable traffic requires automatic buffering and scaling
  • Built-in observability, replay capabilities, and retry management are priorities
  • You want to eliminate webhook infrastructure maintenance

Both tools excel in their primary domains. Supabase Queues provides powerful database-integrated queuing for internal workflows. Hookdeck Event Gateway delivers enterprise-grade webhook reliability and observability without the engineering overhead of building it yourself.

For most teams dealing with external webhooks, Hookdeck Event Gateway's managed approach proves faster to implement and easier to maintain than building webhook infrastructure on top of Supabase Queues. You can get started with Hookdeck's free tier to see how it handles your webhook and event delivery workflows.

Frequently Asked Questions

Can I use Hookdeck with Supabase? Yes. Hookdeck Event Gateway can receive external webhooks and forward them to Supabase Edge Functions or directly into Supabase Queues for processing.

Is Supabase Queues free? Supabase Queues is included in your Supabase database tier with no separate pricing. Costs depend on your overall database usage.

Does Hookdeck support retry limits? Yes. Hookdeck includes configurable retry policies with exponential backoff, maximum retry attempts, and automatic handling for failed events.

Which is better for high-volume webhooks? Hookdeck Event Gateway is purpose-built for high-volume external webhooks with automatic scaling, buffering, and rate limiting to protect downstream services.

Can Supabase Queues handle external webhooks? Yes, but you'll need to build webhook receiver infrastructure including signature verification, retry logic, and CRON-based processing functions.