Destination type

PostgreSQL Destination

Use PostgreSQL destinations to insert every routed event directly into an existing PostgreSQL table for operational data, audit streams, analytics staging, or database-driven workflows.

POSTGRESQLPostgreSQL tableINSERTDatabases
PostgreSQL destination delivery flowFastHook routes accepted events through a connection to a PostgreSQL destination and stores delivery attempt evidence.AcceptedRequestsource verifiedConnectionRoute branchfilters and transformsretry policyPOSTGRESQLPostgreSQLPostgreSQL tableINSERTAttemptEvidencestatus + bodyDestination config owns target, auth, method, rate limit, disabled state, and delivery attempt evidence.
For PostgreSQL destinations, FastHook opens a short-lived database connection, performs one parameterized INSERT, closes the connection, and stores the result as delivery attempt evidence.

When to use this destination type

Choose POSTGRESQL when the receiver target is PostgreSQL table. Destinations are outbound delivery targets: they do not decide whether a source request should be accepted, and they do not own connection filters or transformations. They own where the final delivery goes and how FastHook should authenticate, pace, disable, and inspect that delivery.

A destination can be reused by multiple connections when several source branches should feed the same receiver. Reuse keeps receiver capacity, credentials, and attempt evidence attached to one destination id.

FastHook dashboard fields

In the dashboard, create a destination, set Destination Type to POSTGRESQL, then fill the fields below.

Host, port, and database

The public PostgreSQL endpoint and database name.

Username and password

Credentials for a role that can insert into only the target table.

TLS mode

Certificate verification is enabled by default.

Schema and table

Existing destination table. FastHook does not create or migrate it.

Column mapping

Payload JSONB plus optional event ID, metadata JSONB, and delivered-at columns.

Duplicate event behavior

Fail duplicates or ignore them using a unique event ID column.

API config fields

The REST API stores destination-specific behavior under config. Use PATCH for focused edits and PUT only when your request contains the full config you want to keep.

config.host / config.port

Publicly reachable PostgreSQL host and TCP port. Port defaults to 5432.

config.database

Database that contains the destination table.

config.schema / config.table

Target schema and table. Schema defaults to public.

config.payload_column

Required JSONB column that receives the routed payload. Defaults to payload.

config.event_id_column

Optional text column for FastHook event IDs and idempotent redelivery handling.

config.metadata_column

Optional JSONB column for request, source, connection, and delivery metadata.

config.delivered_at_column

Optional timestamptz column for the insertion time.

config.on_conflict

ERROR or IGNORE. IGNORE requires event_id_column and a matching unique constraint.

config.ssl_mode

verify-full (default) or disable. Cloudflare Workers does not expose a TLS-without-verification mode for PostgreSQL sockets.

config.connect_timeout_seconds

Connection and authentication timeout from 1 to 30 seconds. Defaults to 10.

config.auth

username and password for a least-privilege PostgreSQL writer role.

config.rate_limit

Delivery pacing. New PostgreSQL destinations default to 5 inserts per second.

PostgreSQL destination config
{
  "name": "postgres-event-store",
  "type": "POSTGRESQL",
  "config": {
    "host": "db.example.com",
    "port": 5432,
    "database": "events",
    "schema": "public",
    "table": "webhook_events",
    "ssl_mode": "verify-full",
    "payload_column": "payload",
    "event_id_column": "event_id",
    "metadata_column": "metadata",
    "delivered_at_column": "delivered_at",
    "on_conflict": "IGNORE",
    "connect_timeout_seconds": 10,
    "auth_type": "POSTGRES_PASSWORD",
    "auth": {
      "username": "fasthook_writer",
      "password": "replace-with-a-secret"
    },
    "rate_limit": 5,
    "rate_limit_period": "second"
  }
}
Create PostgreSQL destination with cURL
curl -X POST "https://api.fasthook.io/v1/destinations" \
  -H "Authorization: Bearer $FASTHOOK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "postgres-event-store",
  "type": "POSTGRESQL",
  "config": {
    "host": "db.example.com",
    "port": 5432,
    "database": "events",
    "schema": "public",
    "table": "webhook_events",
    "ssl_mode": "verify-full",
    "payload_column": "payload",
    "event_id_column": "event_id",
    "metadata_column": "metadata",
    "delivered_at_column": "delivered_at",
    "on_conflict": "IGNORE",
    "connect_timeout_seconds": 10,
    "auth_type": "POSTGRES_PASSWORD",
    "auth": {
      "username": "fasthook_writer",
      "password": "replace-with-a-secret"
    },
    "rate_limit": 5,
    "rate_limit_period": "second"
  }
}'

Create the table and writer role

Run this once as a database administrator, replace the example password, and store that writer password in the destination. FastHook never creates or migrates the target table.

PostgreSQL destination table and writer role
CREATE TABLE public.webhook_events (
  event_id text PRIMARY KEY,
  payload jsonb NOT NULL,
  metadata jsonb,
  delivered_at timestamptz NOT NULL DEFAULT now()
);

CREATE ROLE fasthook_writer LOGIN PASSWORD 'replace-with-a-secret';
GRANT CONNECT ON DATABASE events TO fasthook_writer;
GRANT USAGE ON SCHEMA public TO fasthook_writer;
GRANT INSERT ON TABLE public.webhook_events TO fasthook_writer;

If you use a different event ID column, keep a UNIQUE or primary-key constraint on that column before setting on_conflict to IGNORE.

Authentication

Destination authentication is outbound. It helps the receiver trust or accept FastHook delivery, and it is separate from source authentication that verifies the original webhook producer.

  • PostgreSQL username and password
  • TLS certificate verification

Delivery operation and rate limit

This destination performs one INSERT per delivery attempt. Rate limits apply at the destination boundary, so every connection that targets the same table destination shares that capacity.

INSERT

Rate limit: Defaults to 5 inserts/second. Configure a lower or higher destination rate for your database capacity.

Delivery behavior

  • FastHook performs one parameterized INSERT per delivery. Payload and metadata values never become SQL syntax.
  • The payload column must be JSONB. Non-JSON source bodies are stored as a JSON string, so every delivery still produces valid JSONB.
  • FastHook opens a short-lived connection inside the send Worker and closes it after the insert. The PostgreSQL driver is loaded only for PostgreSQL deliveries.
  • The target must be publicly reachable. Cloudflare Workers block localhost and private-network addresses for ordinary outbound TCP sockets.
  • FastHook does not create tables, columns, indexes, or constraints. Apply the documented DDL before enabling the connection.
  • Delivery is at least once. Use event_id_column, a UNIQUE constraint, and on_conflict=IGNORE when side effects must be idempotent.

Setup checklist

  1. Create the destination table and a least-privilege writer role in PostgreSQL.
  2. Allow inbound PostgreSQL traffic from public networks according to your database provider's networking controls. Workers outbound TCP does not use a fixed single IP.
  3. Create a FastHook destination and choose POSTGRESQL.
  4. Enter host, port, database, credentials, TLS mode, schema, table, and column names.
  5. For idempotency, configure an event ID column with a UNIQUE constraint and choose Ignore by Event ID.
  6. Connect a source, send a test event, and inspect the attempt before routing production traffic.

Troubleshooting

Connection times out or the address is rejected.

Use a public hostname, allow the database port in provider firewall controls, and confirm the database accepts public TCP connections. Localhost and private IP addresses are blocked by Cloudflare Workers.

TLS or certificate verification fails.

Use verify-full with a publicly trusted certificate whose hostname matches config.host. Use disable only for an endpoint where unencrypted database traffic is explicitly acceptable.

Insert fails with permission denied or relation does not exist.

Confirm database, schema, and table names and grant the writer role CONNECT, USAGE on the schema, and INSERT on the table.

Duplicate deliveries create duplicate rows.

Add a UNIQUE constraint to the event ID column, set event_id_column, and use on_conflict=IGNORE.

Insert fails because a column has the wrong type.

Use JSONB for payload and metadata, text for event ID, and timestamptz for delivered_at.

Related docs