Ingest data into Materialize via HTTP POST requests from webhook providers
Webhook sources allow external applications to push data directly into Materialize via HTTP POST requests. Each webhook source exposes a unique, publicly accessible URL that accepts JSON, text, or binary data.
CREATE SOURCE webhook_source FROM WEBHOOK BODY FORMAT JSON CHECK ( WITH ( HEADERS, BODY AS request_body, SECRET webhook_secret AS validation_secret ) constant_time_eq(headers->'x-api-key', validation_secret) );
CREATE SECRET basic_auth AS 'Basic dXNlcjpwYXNzd29yZA==';CREATE SOURCE webhook_basic FROM WEBHOOK BODY FORMAT JSON CHECK ( WITH (HEADERS, SECRET basic_auth AS auth_value) constant_time_eq(headers->'authorization', auth_value) );
Validate requests using HMAC signatures (common with Stripe, GitHub, etc.):
CREATE SECRET webhook_signing_key AS 'your_signing_key';CREATE SOURCE webhook_hmac FROM WEBHOOK BODY FORMAT JSON CHECK ( WITH ( HEADERS, BODY AS request_body, SECRET webhook_signing_key AS signing_key ) constant_time_eq( decode(headers->'x-signature', 'base64'), hmac(request_body, signing_key, 'sha256') ) );
CREATE SOURCE webhook_custom FROM WEBHOOK BODY FORMAT JSON CHECK ( WITH ( HEADERS, BODY AS request_body, SECRET webhook_secret AS secret_value ) -- Check API key AND timestamp freshness constant_time_eq(headers->'x-api-key', secret_value) AND (headers->'x-timestamp')::bigint > extract(epoch from now()) - 300 );
Use constant_time_eq() instead of = for security-sensitive comparisons to prevent timing attacks.
CREATE SOURCE webhook_with_headers FROM WEBHOOK BODY FORMAT JSON INCLUDE HEADER 'x-request-id' AS request_id, INCLUDE HEADER 'x-timestamp' AS timestamp;
-- Webhook receives partial events with the same IDCREATE MATERIALIZED VIEW events_merged ASSELECT DISTINCT ON (id) *FROM ( SELECT body->>'id' AS id, try_parse_monotonic_iso8601_timestamp(body->>'started_at') AS started_at, try_parse_monotonic_iso8601_timestamp(body->>'finished_at') AS finished_at FROM webhook_source)ORDER BY id, finished_at NULLS LAST, started_at NULLS LAST;
CREATE SECRET stripe_webhook_secret AS 'whsec_...';CREATE SOURCE stripe_events FROM WEBHOOK BODY FORMAT JSON INCLUDE HEADER 'stripe-signature' AS signature CHECK ( WITH ( HEADERS, BODY AS request_body, SECRET stripe_webhook_secret AS signing_secret ) -- Stripe signature validation -- (simplified - production should parse timestamp and signatures) constant_time_eq(headers->'stripe-signature', signing_secret) );
CREATE SECRET iot_api_key AS 'sensor_fleet_key_123';CREATE SOURCE sensor_readings FROM WEBHOOK BODY FORMAT JSON INCLUDE HEADER 'x-device-id' AS device_id, INCLUDE HEADER 'x-timestamp' AS reading_timestamp CHECK ( WITH ( HEADERS, SECRET iot_api_key AS api_key ) constant_time_eq(headers->'x-api-key', api_key) );CREATE VIEW sensor_data_parsed ASSELECT device_id, (body->>'temperature')::numeric AS temperature, (body->>'humidity')::numeric AS humidity, (body->>'pressure')::numeric AS pressure, try_parse_monotonic_iso8601_timestamp(reading_timestamp) AS timestampFROM sensor_readings;
CREATE SECRET github_webhook_secret AS 'your_github_secret';CREATE SOURCE github_events FROM WEBHOOK BODY FORMAT JSON INCLUDE HEADER 'x-github-event' AS event_type, INCLUDE HEADER 'x-github-delivery' AS delivery_id CHECK ( WITH ( HEADERS, BODY AS request_body, SECRET github_webhook_secret AS secret ) constant_time_eq( decode(substring(headers->'x-hub-signature-256' from 8), 'hex'), hmac(request_body, secret, 'sha256') ) );