Supabase Webhook Setup and Debugging

Supabase offers a powerful backend for your applications, providing a PostgreSQL database, authentication, storage, and real-time capabilities. A common pattern in modern, event-driven architectures is to react to changes in your database or authentication system. This is where webhooks come in, allowing Supabase to notify external services or internal functions about specific events, like a new user signup or a row update.

While incredibly powerful, setting up and debugging webhooks can often feel like navigating a maze blindfolded. You trigger an event, but nothing happens on the other end, or the data arrives in an unexpected format. This article will guide you through setting up Supabase webhooks and, crucially, how to effectively debug them when things inevitably go sideways.

Understanding Supabase Webhooks

At its core, Supabase leverages PostgreSQL's robust trigger system to initiate webhooks. When a specified event occurs (e.g., INSERT, UPDATE, DELETE on a table), a database trigger fires. This trigger can then make an HTTP request to an external endpoint.

The primary mechanism for this external communication within Supabase's PostgreSQL instance is the pg_net extension. This extension allows your database to make HTTP requests, turning your database events into network calls. You can either use pg_net directly or leverage libraries like supabase-db-triggers which simplify the process of creating triggers that send data to Supabase Edge Functions or other HTTP endpoints.

Common use cases for Supabase webhooks include: * Notifying a third-party CRM when a new user registers. * Triggering a serverless function to process an uploaded file. * Sending real-time notifications to users when a specific database record changes. * Integrating with analytics platforms.

Setting Up a Basic Supabase Webhook

Let's walk through a concrete example: sending a webhook when a new user signs up in your Supabase project. We'll configure a database trigger on the auth.users table to send a POST request to an endpoint whenever a new user is inserted. For initial debugging, we'll use a Hookpeek URL as our temporary endpoint.

Example 1: Database Trigger for New User Signup

First, ensure the pg_net extension is enabled in your Supabase project. Go to Database -> Extensions in your Supabase dashboard and enable pg_net.

Next, we'll create a function and a trigger. This setup will capture the id and email of a new user and send it as a JSON payload.

-- 1. Create a function to send the webhook
CREATE OR REPLACE FUNCTION public.handle_new_user()
RETURNS TRIGGER AS $$
DECLARE
    webhook_url TEXT := 'https://webhook.91-99-176-101.nip.io/YOUR_UNIQUE_HOOKPEEK_PATH'; -- Replace with your Hookpeek URL
    payload JSONB;
BEGIN
    payload := jsonb_build_object(
        'event', 'user_signed_up',
        'user_id', NEW.id,
        'email', NEW.email,
        'created_at', NEW.created_at
    );

    PERFORM net.http_post(
        url => webhook_url,
        body => payload,
        headers => jsonb_build_object('Content-Type', 'application/json')
    );

    RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- 2. Create the trigger on the auth.users table
CREATE OR REPLACE TRIGGER on_auth_user_created
AFTER INSERT ON auth.users
FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();

Explanation: * We define a PL/pgSQL function handle_new_user that constructs a JSON payload containing details of the new user (NEW refers to the new row inserted). * net.http_post is the key pg_net function that dispatches the HTTP request. We specify the url, the body (our JSON payload), and headers. * The on_auth_user_created trigger is set to fire AFTER INSERT on the auth.users table for EACH ROW. This means every time a new user record is added, our function will execute. * SECURITY DEFINER is important here, as auth.users is a protected table. This ensures the function runs with the privileges of the user who created it (typically the supabase_admin role), allowing it to read from auth.users.

To test this, replace YOUR_UNIQUE_HOOKPEEK_PATH with a unique path you generate on Hookpeek (e.g., https://webhook.91-99-176-101.nip.io/my-supabase-test). Then, sign up a new user through your Supabase project's authentication UI or API. You should see the incoming request appear instantly in your Hookpeek inbox.

The Debugging Gauntlet: Common Supabase Webhook Pitfalls

Setting up the happy path is one thing; dealing with silent failures or unexpected behavior is another. Here are common pitfalls you'll encounter when working with Supabase webhooks:

  • pg_net Not Enabled: The most basic step, but easily overlooked. If pg_net isn't enabled, your net.http_post calls will fail.
  • Incorrect Trigger Definition:
    • FOR EACH ROW vs. FOR EACH STATEMENT: Most webhooks need FOR EACH ROW to access NEW or OLD row data.
    • Trigger timing (BEFORE, AFTER): Choose AFTER if you need the row to be fully committed before sending, which is usually the case for webhooks.
    • Trigger conditions (WHEN): If your trigger has a WHEN clause, ensure it evaluates correctly.
  • Network Issues:
    • Unreachable Endpoint: Is your target URL publicly accessible? Is there a firewall blocking Supabase's egress traffic to your endpoint?
    • DNS Resolution: Is the hostname in your webhook URL resolving correctly?
    • TLS/SSL Issues: If your endpoint uses HTTPS, ensure its certificate is valid and trusted.
  • Payload Mismatches:
    • Incorrect Content-Type Header: Your receiving service expects a certain content type (e.g., application/json). If your trigger sends text/plain or omits the header, the receiver might reject it.
    • Malformed JSON: If you're constructing JSON manually, ensure it's valid. jsonb_build_object typically handles this well, but custom string concatenation can lead to issues.
    • Missing or Unexpected Fields: The receiver might expect specific fields that aren't present in your payload, or it might struggle with extra, unexpected fields.
  • Authentication/Authorization: If your webhook endpoint requires an API key or a bearer token, ensure it's correctly included in the headers of your net.http_post call.
  • Rate Limiting: Your receiving service might rate-limit incoming requests. If Supabase sends a burst of webhooks, some might be rejected with 429 Too Many Requests.
  • Supabase Edge Function Specifics: If your webhook targets an Edge Function, consider cold starts, timeouts (default 10s), and memory limits. Your Edge Function might be failing silently.
  • Permissions: The user executing the trigger function (often supabase_admin or the SECURITY DEFINER role) needs appropriate permissions to read the data it's trying to send.

Debugging with Hookpeek: Your Webhook Sidekick

When a webhook isn't behaving as expected, you need visibility into what's actually being sent. This is where Hookpeek shines. Instead of guessing, you can inspect the raw request and understand exactly what Supabase is dispatching.

Example 2: Debugging a Failing Supabase Webhook with Hookpeek

Imagine your Supabase webhook is supposed to send user data to a third-party CRM, but the CRM isn't creating new user records. You suspect the webhook payload or headers are