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.
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 databaseThe public PostgreSQL endpoint and database name.
Username and passwordCredentials for a role that can insert into only the target table.
TLS modeCertificate verification is enabled by default.
Schema and tableExisting destination table. FastHook does not create or migrate it.
Column mappingPayload JSONB plus optional event ID, metadata JSONB, and delivered-at columns.
Duplicate event behaviorFail 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.portPublicly reachable PostgreSQL host and TCP port. Port defaults to 5432.
config.databaseDatabase that contains the destination table.
config.schema / config.tableTarget schema and table. Schema defaults to public.
config.payload_columnRequired JSONB column that receives the routed payload. Defaults to payload.
config.event_id_columnOptional text column for FastHook event IDs and idempotent redelivery handling.
config.metadata_columnOptional JSONB column for request, source, connection, and delivery metadata.
config.delivered_at_columnOptional timestamptz column for the insertion time.
config.on_conflictERROR or IGNORE. IGNORE requires event_id_column and a matching unique constraint.
config.ssl_modeverify-full (default) or disable. Cloudflare Workers does not expose a TLS-without-verification mode for PostgreSQL sockets.
config.connect_timeout_secondsConnection and authentication timeout from 1 to 30 seconds. Defaults to 10.
config.authusername and password for a least-privilege PostgreSQL writer role.
config.rate_limitDelivery pacing. New PostgreSQL destinations default to 5 inserts per second.
{
"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"
}
}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.
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.
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
- Create the destination table and a least-privilege writer role in PostgreSQL.
- 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.
- Create a FastHook destination and choose POSTGRESQL.
- Enter host, port, database, credentials, TLS mode, schema, table, and column names.
- For idempotency, configure an event ID column with a UNIQUE constraint and choose Ignore by Event ID.
- 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.