# Storage Layer

## Introduction

Webhooked supports multiple storage backends for persisting webhook data. You can write to one or multiple backends simultaneously, with each backend supporting its own formatting and transformation.

## Storage Context Variables

Variables available in security conditions:

| Variable             | Description                                            | Example                                   |
| -------------------- | ------------------------------------------------------ | ----------------------------------------- |
| `.SpecName`          | Name of the current spec as defined in config          | `"user-events"`                           |
| `.SpecEntrypointURL` | EntrypointURL of the current spec as defined in config | `"/user-events"`                          |
| `.ConnID`            | Unique connection ID                                   | `123549841`                               |
| `.ConnTime`          | Connection established time                            | time.Time object `2025-08-20T21:10:00Z`   |
| `.Host`              | Host header of request                                 | `"example.com"`                           |
| `.IsTLS`             | Whether request is HTTPS                               | `true`                                    |
| `.Method`            | HTTP method used                                       | `"POST"`                                  |
| `.Payload`           | Raw request body                                       | `{"data": "value"}`                       |
| `.QueryArgs`         | Query parameters object                                | `fasthttp.Args{"id":"123","token":"abc"}` |
| `.RemoteAddr`        | Remote Addr                                            | `"192.168.1.10:54321"`                    |
| `.RemoteIP`          | Remote network address                                 | `"192.168.1.1"`                           |
| `.RequestTime`       | Time when request was received                         | time.Time object `2025-08-20T21:10:00Z`   |
| `.Request`           | Full `fasthttp.Request` object                         | `&fasthttp.Request{...}`                  |
| `.URI`               | Request URI                                            | `"/webhooks/..."`                         |
| `.UserAgent`         | Client User-Agent header                               | `"Mozilla/5.0 (X11; Linux x86_64)"`       |
| `.StorageType`       | Type as defined in spec                                | `"redis"`                                 |

## Storages Providers

### Redis

```yaml
storage:
  - type: redis
    specs:
      host: localhost # Valuable - see doc "Sourcing (Valuable)" for more info
      port: 6379      # Valuable - see doc "Sourcing (Valuable)" for more info
      username: user  # Valuable - see doc "Sourcing (Valuable)" for more info
      password: pass  # Valuable - see doc "Sourcing (Valuable)" for more info
      database: 0     # integer
      key: "webhooks:events" # string
```

<table><thead><tr><th width="113">Variable</th><th width="98">Type</th><th width="112" data-type="checkbox">Valuable</th><th width="123" data-type="checkbox">Formatting</th><th>Description</th></tr></thead><tbody><tr><td><code>host</code></td><td>string</td><td>true</td><td>false</td><td>Host of the redis server</td></tr><tr><td><code>port</code></td><td>integer</td><td>true</td><td>false</td><td>Port of the redis server</td></tr><tr><td><code>username</code></td><td>string</td><td>true</td><td>false</td><td>Username to connect to redis</td></tr><tr><td><code>password</code></td><td>string</td><td>true</td><td>false</td><td>Password to connect to redis</td></tr><tr><td><code>database</code></td><td>integer</td><td>false</td><td>false</td><td>Database to use (default to <code>0</code>)</td></tr><tr><td><code>key</code></td><td>string</td><td>false</td><td>false</td><td>Key used to store with <code>RPush</code> command</td></tr></tbody></table>

### PostgreSQL

```yaml
storage:
  - type: postgres
    specs:
      databaseUrl: postgres://user:password@localhost:5432/webhooks?sslmode=disable
      query: |
        INSERT INTO webhook_events (name, payload, received_at)
        VALUES (:name, :payload, NOW())
      args:
        name: "{{ .WebhookName }}"
        payload: "{{ .Payload }}"
```

<table><thead><tr><th width="113">Variable</th><th width="98">Type</th><th width="112" data-type="checkbox">Valuable</th><th width="122" data-type="checkbox">Formatting</th><th>Description</th></tr></thead><tbody><tr><td><code>databaseUrl</code></td><td>string</td><td>true</td><td>false</td><td>Database URL of the postgres server</td></tr><tr><td><code>query</code></td><td>string</td><td>false</td><td>false</td><td>The query to execute with named arguments</td></tr><tr><td><code>args</code></td><td>map</td><td>false</td><td>true</td><td>Map of naed arguments used in the query</td></tr></tbody></table>

### RabbitMQ Storage

```yaml
storage:
  - type: rabbitmq
    specs:
      databaseUrl: amqp://user:password@localhost:5672/
      queueName: webhook-events
      maxAttempt: 10
      durable: true
      deleteWhenUnused: false
      exclusive: false
      noWait: false
      exchange: webhooks
      contentType: text
      mandatory: false
      immediate: false
```

<table><thead><tr><th width="162">Variable</th><th width="98">Type</th><th width="112" data-type="checkbox">Valuable</th><th width="122" data-type="checkbox">Formatting</th><th>Description</th></tr></thead><tbody><tr><td><code>databaseUrl</code></td><td>string</td><td>true</td><td>false</td><td>Database URL of the rabbitmq server</td></tr><tr><td><code>queueName</code></td><td>string</td><td>false</td><td>false</td><td>Name of the queue</td></tr><tr><td><code>maxAttempt</code></td><td>integer</td><td>false</td><td>false</td><td>Maximum retry attempts</td></tr><tr><td><code>durable</code></td><td>bool</td><td>false</td><td>false</td><td>Survive broker restart</td></tr><tr><td><code>deleteWhenUnused</code></td><td>bool</td><td>false</td><td>false</td><td>Don't delete when unused</td></tr><tr><td><code>exclusive</code></td><td>bool</td><td>false</td><td>false</td><td>Not exclusive to connection</td></tr><tr><td><code>noWait</code></td><td>bool</td><td>false</td><td>false</td><td>Wait for confirmation</td></tr><tr><td><code>exchange</code></td><td>string</td><td>false</td><td>false</td><td>Exchange to bind the queue</td></tr><tr><td><code>contentType</code></td><td>string</td><td>false</td><td>false</td><td>Content type for messages</td></tr><tr><td><code>mandatory</code></td><td>bool</td><td>false</td><td>false</td><td>Return message if not routable</td></tr><tr><td><code>immediate</code></td><td>bool</td><td>false</td><td>false</td><td>Deliver immediately or fail</td></tr></tbody></table>

{% hint style="warning" %}
IMPORTANT: Configuration of the queue and exchange must match your existing declaration. If no queue are present, webhooked try to defined it.
{% endhint %}

### NoOp

NoOp storage is for testing and development where persistence isn't needed.

```yaml
storage:
  - type: noop
```

## Storage Formatting

Each storage can have its own data transformation.

```yaml
storage:
  # Raw JSON to Redis
  - type: redis
    specs: # ...
    formatting:
      templateString: "{{ .Payload }}"
  
  # Structured data to RabbitMQ
  - type: rabbitmq
    specs: # ...
    formatting:
      templateString: |
        {
          "id": "{{ (fromJSON .Payload).id }}",
          "type": "{{ (fromJSON .Payload).type }}",
          "timestamp": "{{ now }}"
        }
```

{% hint style="info" %}
See more about formating on the dedicated page : [Formatting](/webhooked/configuration/formatting.md)
{% endhint %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.atom.codes/webhooked/configuration/storage-layer.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
