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

# Record API

> Implementing a Prequel Destination for your record-based receiving endpoint

A `webhook_record` destination delivers one HTTP request per record.

For the API call to register a destination with Prequel, including a complete example, see [Create Destination](/import/import-api/overview).

For higher-volume workloads, consider the [Batch API](/import/destination-specs/webhook-batch), which delivers multiple records per request.

## Authentication

Prequel signs every delivery request with the `X-Prequel-Webhook-Signature` header so your endpoint can validate the request originated from Prequel. Fetch the public key for verification from `GET /public/signatures/webhook-public-key`. See [Webhook headers](#webhook-headers) for the full header list and verification steps.

## Creating your destination spec

The `destination` object allows you to customize the contract between data shared by your customers and the shape Prequel provides to your receiving endpoint.

**Example:** Suppose you are importing a `users` table with string fields `id`, `email`, and `subscription`.

Below are four possible shapes for a `webhook_record` destination. These are not exhaustive and features can be combined to conform to your endpoint's requirements:

1. **Schema as body:** Each request body is the record in the shape declared by `record_schema`, with no additional shaping.
2. **Custom body:** Use a `body` template to customize each record.
3. **Routed by provider:** `uri` and/or `headers` vary at delivery time using the `ProviderID` delivery-level identifier.
4. **Destination per provider:** A separate destination is registered for each provider, with a `record_schema` that includes provider-specific custom fields.

<Tabs>
  <Tab title="Schema as body" icon="paper-plane">
    ```json title="POST destination payload" icon="brackets-curly" expandable theme={null}
    {
      "name": "users-record-destination",
      "type": "webhook_record",
      "record_schema": {
        "type": "object",
        "properties": {
          "id":    { "type": "string" },
          "email": { "type": "string" },
          "subscription": { "type": "string" }
        },
        "required": ["id", "email"]
      },
      "webhook_record": {
        "request_template": {
          "method": "POST",
          "uri": "https://example.com/prequel/records",
          "headers": { "Content-Type": "application/json" }
        }
      }
    }
    ```

    <Steps>
      <Step title="Name your destination">
        Set `name` (string, required) to a unique identifier for this destination within your account.
      </Step>

      <Step title="Set the destination type">
        Set `type` (string, required) to `webhook_record` for per-record deliveries.
      </Step>

      <Step title="Declare your record schema">
        Set `record_schema` (object, required) to a [JSON Schema (draft-07)](https://json-schema.org/specification-links#draft-7) document that declares the fields each record contains. Prequel validates records against this schema before delivery.
      </Step>

      <Step title="Configure the request template">
        Set `webhook_record.request_template` (object, required) with the HTTP `method` (string, defaults to `POST`), `uri` (string, required), and optional `headers` (object). The `uri` and `headers` templates support the `.Prequel.*` namespace, `.Record.*` fields, and `.IsDeleted`. With no `body` template, Prequel sends each record as a JSON object.
      </Step>

      <Step title="Tune optional throughput limits">
        Optionally set `webhook_record.max_records_per_minute` (integer, default `3000`) and `webhook_record.max_concurrency` (integer, default `1`) to control delivery throughput.
      </Step>
    </Steps>
  </Tab>

  <Tab title="Custom body" icon="pen-to-square">
    ```json title="POST destination payload" icon="brackets-curly" expandable theme={null}
    {
      "name": "users-record-destination",
      "type": "webhook_record",
      "record_schema": {
        "type": "object",
        "properties": {
          "id":    { "type": "string" },
          "email": { "type": "string" },
          "subscription": { "type": "string" }
        },
        "required": ["id", "email"]
      },
      "webhook_record": {
        "request_template": {
          "method": "POST",
          "uri": "https://example.com/prequel/records",
          "headers": { "Content-Type": "application/json" },
          "body": "{\"email\": \"{{.Record.email}}\", \"user\": {{.Record.AsJson}}, \"is_deleted\": {{.IsDeleted}}, \"provider_id\": \"{{.Prequel.ProviderID}}\", \"load_id\": \"{{.Prequel.LoadID}}\"}"
        }
      }
    }
    ```

    <Steps>
      <Step title="Name your destination">
        Set `name` (string, required) to a unique identifier for this destination within your account.
      </Step>

      <Step title="Set the destination type">
        Set `type` (string, required) to `webhook_record` for per-record deliveries.
      </Step>

      <Step title="Declare your record schema">
        Set `record_schema` (object, required) to a [JSON Schema (draft-07)](https://json-schema.org/specification-links#draft-7) document that declares the fields each record contains. Prequel validates records against this schema before delivery.
      </Step>

      <Step title="Configure the request template with a body template">
        Set `webhook_record.request_template` (object, required) with `method`, `uri`, `headers`, and `body`. The `body` template renders once per request and becomes the HTTP body, with access to record fields, the deletion flag, and delivery-level identifiers. The `uri` and `headers` templates have access to the same variables, scoped to `.Prequel.*`, `.Record.*`, and `.IsDeleted`. See [Template variables](#template-variables) for the full list of available variables. To validate delivery context, render values like `{{.Prequel.ProviderID}}` and `{{.Prequel.LoadID}}` into the body and check them on receipt.
      </Step>

      <Step title="Tune optional throughput limits">
        Optionally set `webhook_record.max_records_per_minute` (integer, default `3000`) and `webhook_record.max_concurrency` (integer, default `1`) to control delivery throughput.
      </Step>
    </Steps>
  </Tab>

  <Tab title="Routed by provider" icon="route">
    ```json title="POST destination payload" icon="brackets-curly" expandable theme={null}
    {
      "name": "users-record-destination",
      "type": "webhook_record",
      "record_schema": {
        "type": "object",
        "properties": {
          "id":    { "type": "string" },
          "email": { "type": "string" },
          "subscription": { "type": "string" }
        },
        "required": ["id", "email"]
      },
      "webhook_record": {
        "request_template": {
          "method": "POST",
          "uri": "https://example.com/prequel/records?provider={{.Prequel.ProviderID}}",
          "headers": { "Content-Type": "application/json" }
        }
      }
    }
    ```

    <Steps>
      <Step title="Name your destination">
        Set `name` (string, required) to a unique identifier for this destination within your account.
      </Step>

      <Step title="Set the destination type">
        Set `type` (string, required) to `webhook_record` for per-record deliveries.
      </Step>

      <Step title="Declare your record schema">
        Set `record_schema` (object, required) to a [JSON Schema (draft-07)](https://json-schema.org/specification-links#draft-7) document that declares the fields each record contains. Prequel validates records against this schema before delivery.
      </Step>

      <Step title="Route by provider in the request template">
        Set `webhook_record.request_template` (object, required) with `method`, `uri`, and optional `headers`. Use `{{.Prequel.ProviderID}}` in `uri` and `headers` to route by the provider whose data is being delivered. `.Record.*` fields and `.IsDeleted` are also available if you need to route by record content. See [Template variables](#template-variables) for the full list of available variables.
      </Step>

      <Step title="Tune optional throughput limits">
        Optionally set `webhook_record.max_records_per_minute` (integer, default `3000`) and `webhook_record.max_concurrency` (integer, default `1`) to control delivery throughput.
      </Step>
    </Steps>
  </Tab>

  <Tab title="Destination per provider" icon="id-card">
    ```json title="POST destination payload" icon="brackets-curly" expandable theme={null}
    {
      "name": "acme-corp-record-destination",
      "type": "webhook_record",
      "record_schema": {
        "type": "object",
        "properties": {
          "id":      { "type": "string" },
          "email":   { "type": "string" },
          "subscription": { "type": "string" },
          "tax_id":  { "type": "string" }
        },
        "required": ["id", "email", "tax_id"]
      },
      "webhook_record": {
        "request_template": {
          "method": "POST",
          "uri": "https://example.com/prequel/records/acme-corp",
          "headers": { "Content-Type": "application/json" }
        }
      }
    }
    ```

    <Steps>
      <Step title="Register one destination per provider">
        Create a separate destination for each provider whose requirements differ. Give each one a `name` (string, required) that identifies the provider it serves.
      </Step>

      <Step title="Set the destination type">
        Set `type` (string, required) to `webhook_record` for per-record deliveries.
      </Step>

      <Step title="Declare a provider-specific record schema">
        Set `record_schema` (object, required) to a [JSON Schema (draft-07)](https://json-schema.org/specification-links#draft-7) document that declares the fields this provider sends, including any custom fields unique to them. Prequel validates records against this schema before delivery.
      </Step>

      <Step title="Configure the request template">
        Set `webhook_record.request_template` (object, required) with `method`, `uri`, and optional `headers`. Because the destination is scoped to a single provider, you can hard-code provider-specific routing into `uri` and `headers` without needing template variables.
      </Step>

      <Step title="Tune optional throughput limits">
        Optionally set `webhook_record.max_records_per_minute` (integer, default `3000`) and `webhook_record.max_concurrency` (integer, default `1`) to control delivery throughput.
      </Step>
    </Steps>
  </Tab>
</Tabs>

## Template variables

The `body`, `uri`, and `headers` templates all have access to every variable below.

| Variable                     | Type      | Description                                                                                                          |
| ---------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------- |
| `{{.Record.<field>}}`        | `any`     | Value of a specific field from the record (e.g. `{{.Record.email}}`). The field must be declared in `record_schema`. |
| `{{.Record.AsJson}}`         | `object`  | The full record serialized as a JSON object.                                                                         |
| `{{.IsDeleted}}`             | `boolean` | `true` when Prequel is delivering a record deletion, `false` otherwise.                                              |
| `{{.Prequel.ProviderID}}`    | `string`  | Identifier for the provider whose data is being delivered.                                                           |
| `{{.Prequel.LoadID}}`        | `string`  | Identifier for the active load job.                                                                                  |
| `{{.Prequel.DestinationID}}` | `string`  | Identifier for the destination receiving this delivery.                                                              |
| `{{.Prequel.StreamID}}`      | `string`  | Identifier for the stream this delivery came from.                                                                   |

## Webhook headers

Prequel includes the following headers on every delivery request.

| Header                        | Description                                                                                                                                                                                                                                               |
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `X-Prequel-Webhook-Signature` | SHA-256 RSA PKCS1 v1.5 signature of the payload. To verify, fetch the public key from `GET /public/signatures/webhook-public-key` and check the hex-decoded signature against the SHA-256 of `<X-Prequel-Webhook-Timestamp>.<raw body>` using PKCS1 v1.5. |
| `X-Prequel-Webhook-Timestamp` | RFC 3339 timestamp of when the request was sent. Reject stale timestamps to prevent replay attacks.                                                                                                                                                       |
| `X-Prequel-Webhook-Digest`    | SHA-256 hash of the raw request body.                                                                                                                                                                                                                     |
| `Content-Type`                | Defaults to `application/json` unless overridden by the `headers` template.                                                                                                                                                                               |

## Response codes

Your endpoint should respond with an appropriate HTTP status code. Prequel uses the response to determine whether to retry delivery.

| Code      | Description                                                                                 |
| --------- | ------------------------------------------------------------------------------------------- |
| `2xx`     | Success. Prequel treats a 2xx response as a successful delivery.                            |
| `400`     | Bad Request. Malformed or missing fields. Prequel will not retry.                           |
| `403`     | Forbidden. Invalid signature or credentials. Prequel will not retry.                        |
| `404`     | Not Found. Endpoint or resource could not be found. Prequel will not retry.                 |
| `413`     | Payload Too Large. Request body exceeds your endpoint's size limit. Prequel will not retry. |
| `422`     | Unprocessable Entity. Validation failed for specific fields. Prequel will not retry.        |
| `429`     | Too Many Requests. Rate limit exceeded. Prequel will back off and retry.                    |
| `431`     | Request Header Fields Too Large. Prequel will not retry.                                    |
| Other 4xx | Other client error. Prequel will not retry.                                                 |
| `500`     | Internal Server Error. Unexpected error during processing. Prequel will retry.              |
| `502`     | Bad Gateway. Upstream error. Prequel will retry.                                            |
| `503`     | Service Unavailable. Prequel will retry.                                                    |
| `504`     | Gateway Timeout. Request timed out. Prequel will retry.                                     |
| Other 5xx | Other server error. Prequel will retry.                                                     |
