---
title: Snowflake
---
# 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](/advanced-endpoints#enabling-advanced-endpoint-types), your customers will see the option to use a Snowflake destination in the [App Portal](/app-portal).

![Snowflake Endpoint Create](/img/advanced-endpoints/snowflake-create.png)

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](https://docs.snowflake.com/en/user-guide/key-pair-auth). 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:

```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:

```sql
CREATE TABLE testdb.testschema.users (
  name TEXT,
  age INT
);
```

If the endpoint receives the following messages:

```json
{
  "eventType": "user.created",
  "payload": "{\"name\": \"John Smith\", \"age\": 34}"
}
```

```json
{
  "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:

```JavaScript
/**
 * @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.](https://docs.snowflake.com/en/developer-guide/sql-api/submitting-requests#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.

![snowflake-output](/img/stream/snowflake-output.png)
