Snowflake
Svix can deliver webhooks directly to a Snowflake table, without your customers having to set up any listener endpoint or write any glue code.
When Advanced Endpoint Types is enabled, your customers will see the option to use a Snowflake destination in the App Portal.

They will be able to configure the connection right in the App Portal:
accountIdentifier— your Snowflake account identifier in<organization>-<account>form (e.g.ab12345-xs67890).userId— the Snowflake user the public key is assigned to.privateKey— the PEM-encoded private key.dbName,schemaName,tableName— the database, schema, and table that receive the rows.
Every batch of webhooks received by the endpoint is inserted into the configured Snowflake table.
Authentication
Svix authenticates to Snowflake using key-pair (JWT) authentication . Generate an RSA key pair, assign the public key to a Snowflake user, and provide the matching private key in the endpoint config.
Destination table
Without a transformation, Svix inserts each webhook into the table identified by dbName, schemaName, and tableName using three columns: id, created_at, and payload. Svix generates a unique id for each row, sets created_at to the insert time, and writes the raw payload to payload.
The table must already exist before you enable the endpoint, which you can create with the following sql:
CREATE TABLE my_database.my_schema.my_table (
id TEXT,
created_at TIMESTAMP,
payload TEXT
);If you use a transformation (below), you control the SQL that runs, so dbName, schemaName, and tableName become optional — the target table is named directly in your statement.
Transformations
Snowflake Endpoints shape each batch of webhooks into a SQL INSERT statement. The transformation returns the statement to run and the bindings it references.
For example, suppose you have a table with the following structure:
CREATE TABLE testdb.testschema.users (
name TEXT,
age INT
);If the endpoint receives the following messages:
{
"eventType": "user.created",
"payload": "{\"name\": \"John Smith\", \"age\": 34}"
}{
"eventType": "user.created",
"payload": "{\"name\": \"Jane Doe\", \"age\": 47}"
}To insert the new users into your testdb.testschema.users table, you’d write transformation code as follows:
/**
* @param input - The input object
* @param input.events - The array of webhooks in the batch. The number of webhooks in the batch is capped by the endpoint's batch size.
* @param input.events[].payload - The message payload (string or JSON)
* @param input.events[].eventType - The message event type (string)
*
* @returns Object describing the SQL to run against Snowflake.
* @returns returns.statement - The SQL statement to execute. Reference bindings by name (e.g. :id), matching the keys in `bindings`.
* @returns returns.bindings - The bindings referenced by the statement. Each binding has a Snowflake `type` (e.g. "TEXT") and a column-oriented `value` array with one entry per row in the batch.
*/
function handler(input) {
let bindings = {
"name": { "type": "TEXT", "value": [] },
"age": { "type": "FIXED", "value": [] },
};
input.events.forEach((event) => {
let name = event.payload.name;
let age = String(event.payload.age); // Note that Snowflake requires all values be sent as Strings
bindings.name.value.push(name);
bindings.age.value.push(age);
});
return {
bindings: bindings,
statement: "INSERT INTO TESTDB.TESTSCHEMA.users (name, age) VALUES (:name, :age);"
};
}input.events is the list of webhooks received by the endpoint, processed in batches.
bindings are column-oriented: each binding lists a Snowflake type and a value array holding one entry per row in the batch. The statement references them by name (:name and :age). For more information which types are allowed, see Using bind variables in a statement.
Because the statement names the table directly, dbName, schemaName, and tableName are optional when a transformation is set. To write different columns, adjust the bindings, the statement, and your table to match.
The transformation above would insert two rows into your table.
