> ## Documentation Index
> Fetch the complete documentation index at: https://docs.novu.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Store webhook events in Amazon S3

> Configure the Amazon S3 webhook connector to archive batched Novu events as objects, including the transformation contract, object key behavior, and verification steps.

export const connectorName_0 = "Amazon S3"

The Amazon S3 connector writes selected Novu webhook events to an S3 bucket. Novu delivers events in batches, and each delivered batch creates one new object in the bucket. Use this connector to build a durable event archive for replay, auditing, or downstream processing with tools that read from S3.

<Note>
  Outbound webhooks are available on [Team and Enterprise plans](https://novu.co/pricing).
</Note>

## Prerequisites

* An existing S3 bucket in the AWS account and region you want to write to
* An AWS access key ID and secret access key for a principal that can write objects to that bucket
* Permission to manage webhook endpoints in the Novu environment you are configuring

### Required AWS permission

The connector writes each batch as an object, so the credentials need the `s3:PutObject` action on the destination bucket. Additional permissions may be required by your own setup, for example KMS key permissions when the bucket enforces server-side encryption with a customer managed key, or permissions needed to satisfy an organizational policy, service control policy, or bucket policy condition.

Novu configures this connector through the embedded webhook portal. The portal collects bucket and credential values and does not create the bucket, apply a bucket policy, or validate an IAM policy document for you.

## Configuration

These are the fields collected for the Amazon S3 connector.

| Dashboard field       | Required | Description                                                                      |
| --------------------- | -------- | -------------------------------------------------------------------------------- |
| **Bucket**            | Yes      | Name of the destination S3 bucket.                                               |
| **Region**            | Yes      | AWS region that hosts the bucket.                                                |
| **Access key ID**     | Yes      | AWS access key ID used to authenticate.                                          |
| **Secret access key** | Yes      | AWS secret access key paired with the access key ID.                             |
| **Transformation**    | Yes      | JavaScript that sets the object key, the object format, and the object contents. |

There is no URL field for this connector. Delivery targets the bucket through the AWS S3 API rather than an HTTPS endpoint you host.

## Transformation contract

Object storage connectors run the handler once per delivery batch.

The handler receives one delivery batch:

* `input.events`, an array whose length is capped by the endpoint batch size
* `input.events[].eventType`, the Novu event type, for example `message.sent`
* `input.events[].payload`, the webhook body

The webhook body wraps the resource in an envelope. `payload.object` is the resource type as a string, such as `"message"`, and the resource itself is under `payload.data.object`. For message events, fields such as `subscriberId` and `channel` are therefore at `event.payload.data.object`. Other event families use different resource shapes, so guard extracted fields with defaults.

The handler must return:

```ts theme={null}
{
  config: {
    format: "jsonl" | "json" | "raw";
    key: string;
  };
  data: unknown[] | string;
}
```

| Return field    | Behavior                                                                                                                   |
| --------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `config.format` | `"jsonl"`, `"json"`, or `"raw"`. Defaults to `"jsonl"`.                                                                    |
| `config.key`    | Object name for this batch. A timestamp is appended after the transformation runs, so each batch produces a unique object. |
| `data`          | The batch contents. Use an array for `"jsonl"` and `"json"`. Use a string for `"raw"`.                                     |

The default template returns the batch unchanged:

```js theme={null}
function handler(input) {
  return {
    config: {
      format: "jsonl",
      key: "object-generated-by-svix",
    },
    data: input.events,
  };
}
```

With `"jsonl"`, each line is one event:

```jsonl theme={null}
{"payload":{"email":"joe@enterprise.io"},"eventType":"user.created"}
{"payload":{"id":12,"timestamp":"2025-07-21T14:23:17.861Z"},"eventType":"user.login"}
```

### Novu example

This example groups objects by date and keeps each payload intact:

```js theme={null}
function handler(input) {
  const partition = new Date().toISOString().slice(0, 10);

  const data = input.events.map((event) => ({
    eventType: event.eventType,
    payload: event.payload,
  }));

  return {
    config: {
      format: "jsonl",
      key: `novu-events/dt=${partition}/events`,
    },
    data,
  };
}
```

The connector appends a timestamp to `config.key`, so use the key for the prefix structure rather than adding a uniqueness suffix.

### Batching behavior

One delivered batch produces one object. Treat its contents as a variable-length batch and read it line by line when using `"jsonl"`.

## Configure in the Dashboard

<Steps>
  <Step>
    ## Prepare the bucket

    Create the destination bucket in the region you plan to configure, and decide on the key prefix you want, for example a date partition. If the bucket enforces encryption with a customer managed key, confirm the credentials can use that key.
  </Step>

  <Step>
    ## Create credentials

    Create an access key for a principal that can perform `s3:PutObject` on the bucket. Store the secret access key securely, because it is entered once into the connector configuration.
  </Step>

  <Step>
    ## Add the endpoint

    Open **[Webhooks](https://dashboard.novu.co/webhooks)** in the Novu Dashboard, select **Endpoints**, click **Add Endpoint**, and choose **{connectorName_0}**.
  </Step>

  <Step>
    ## Enter connection details

    Enter **Bucket**, **Region**, **Access key ID**, and **Secret access key**. Add a description that identifies the destination, for example the bucket name and the AWS account it belongs to.
  </Step>

  <Step>
    ## Configure the transformation

    Start from the provided template. Change `config.key` to the prefix you want and keep `config.format` aligned with the type of `data` you return.
  </Step>

  <Step>
    ## Select event types

    Select the [event types](/platform/developer/webhooks/event-types) you want to archive. Only selected event types reach the endpoint and its transformation, so start with one event type when validating a new bucket.
  </Step>

  <Step>
    ## Test the endpoint

    Create the endpoint, open **Testing**, and send an example for a subscribed event type. Confirm the attempt succeeded in **Logs**, then list the bucket under your key prefix and confirm a new timestamp-suffixed object exists. Download the object and confirm its contents match the format you configured.
  </Step>
</Steps>

## Verify delivery

Check the following in AWS after a successful test:

* The object exists under the prefix you set in `config.key`, with a timestamp appended.
* The object contents parse in the format you selected, one JSON document per line for `"jsonl"`.
* The object count grows by one per delivered batch rather than one per event.

## Troubleshooting

* **Access denied on delivery**: The credentials cannot perform `s3:PutObject` on this bucket. Check the bucket policy, the identity policy, and any condition that restricts the caller, prefix, or encryption headers.
* **The bucket or region is rejected**: Confirm the bucket name is exact and the region matches the bucket's actual region. A bucket in another region will not accept the request.
* **Delivery succeeds but no object appears**: You are likely looking at the wrong prefix. Compare the transformed output in **Logs** with the `config.key` value, then list the bucket at that prefix.
* **Object contents are not what you expect**: Confirm `config.format` matches the type of `data`. For `"raw"`, `data` must be a string containing the exact contents.
* **Encrypted buckets fail**: If the bucket requires a customer managed KMS key, the credentials also need permission to use that key.
* **Some event types never appear**: Review the endpoint's selected event types, and test each event type separately from the **Testing** tab.

## Related

* [Webhook connectors overview](/platform/developer/webhooks/connectors)
* [Webhook event types](/platform/developer/webhooks/event-types)
* [Webhook delivery, retries, and recovery](/platform/developer/webhooks/webhooks#recovering-and-resending-failed-messages)

## Official references

* [Svix object storage endpoints](https://docs.svix.com/advanced-endpoints/object-storage)
* [Svix advanced endpoint types](https://docs.svix.com/advanced-endpoints)
* [Amazon S3 documentation](https://docs.aws.amazon.com/s3/)
