# Submitting Reports via HTTP
Source: https://docs.chain.link/cre/guides/workflow/using-http-client/submitting-reports-http-go
Last Updated: 2026-05-20

> For the complete documentation index, see [llms.txt](/llms.txt).

This guide is for the **sender** side: a CRE workflow that **creates** a signed report and **POSTs** it to an HTTP endpoint.

**What you'll learn:**

- How to use [`SendReport()`](/cre/reference/sdk/http-client-go#sendrequestersendreport) to submit reports via HTTP
- How to write transformation functions for different API formats
- Best practices for report submission and deduplication

> **NOTE: Need onchain submission instead?**
>
> This guide covers HTTP submission. For submitting reports to smart contracts, see [Submitting Reports Onchain](/cre/guides/workflow/using-evm-client/onchain-write/submitting-reports-onchain).

## What is a CRE report?

A **[CRE report](/cre/key-terms#report-cre-report)** is the signed output your workflow DON produces when you call [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values). It packages your encoded data (the payload you computed in the callback), fixed workflow metadata, a report context (config digest and sequence number), and ECDSA signatures from DON nodes.

This guide covers the **sender** path: create the report in your workflow, then POST it to an HTTP endpoint. The receiver must verify those signatures before trusting the data. See [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-go) on the receiver side.

## Where this guide fits

| Question                    | Answer                                                                                                                                                                                                                                                                                                                |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| What is the report?         | A [CRE report](/cre/key-terms#report-cre-report): output of [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values) after DON consensus (encoded payload + metadata + signatures).                                                                         |
| Where does it come from?    | **Inside this workflow:** after your logic runs (fetch data, compute, encode). There is no separate "get report" step.                                                                                                                                                                                                |
| What does this guide cover? | Steps 3–4 below: [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values), then [`SendReport()`](/cre/reference/sdk/http-client-go#sendrequestersendreport) to your API. Steps 1–2 (trigger and your callback logic) are prerequisites, not the focus here. |
| Who verifies it?            | The **receiver:** your HTTP service or a separate CRE workflow. See [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-go).                                                                                                                                           |

**Sender flow in one workflow execution:**

1. Trigger fires (cron, HTTP, …).
2. Your callback runs (API calls, encoding, etc.).
3. [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values): DON produces a signed `sdk.ReportResponse`.
4. [`SendReport()`](/cre/reference/sdk/http-client-go#sendrequestersendreport): format and POST to your URL.

## Prerequisites

- Familiarity with [making POST requests](/cre/guides/workflow/using-http-client/post-request)
- Understanding of [generating reports](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values)
- A generated report from [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values)

> **CAUTION: Redirects are not supported**
>
> HTTP requests to URLs that return redirects (3xx status codes) will fail. Ensure the URL you provide is the final destination and does not redirect to another URL.

## Payload contract (if you verify offchain)

Receivers using [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-go) expect JSON with hex fields **without** `0x`. Use JSON key `context` for `reportContext`:

| JSON field   | SDK field on `ReportResponse` |
| ------------ | ----------------------------- |
| `report`     | `RawReport`                   |
| `context`    | `ReportContext`               |
| `signatures` | per-node signatures in `Sigs` |

## Quick start: Minimal example (binary only)

Posts **raw `RawReport` bytes** only. Not compatible with the verify guide’s JSON receiver. For local testing, use the [complete working example](#complete-working-example) or [Pattern 4 for offchain verification (hex)](#pattern-4-for-offchain-verification-hex).

Here's the simplest workflow that generates and submits a report via HTTP:

```go
func formatReportSimple(r *sdk.ReportResponse) (*http.Request, error) {
    return &http.Request{
        Url:    "https://api.example.com/reports",
        Method: "POST",
        Body:   r.RawReport,  // Send the raw report bytes
        Headers: map[string]string{
            "Content-Type": "application/octet-stream",
        },
        CacheSettings: &http.CacheSettings{
            Store:  true,
            MaxAge: durationpb.New(60 * time.Second),
        },
    }, nil
}

func submitReport(config *Config, logger *slog.Logger, sendRequester *http.SendRequester, report *cre.Report) (*SubmitResponse, error) {
    resp, err := sendRequester.SendReport(*report, formatReportSimple).Await()
    if err != nil {
        return nil, fmt.Errorf("failed to send report: %w", err)
    }

    return &SubmitResponse{Success: true}, nil
}
```

**What's happening here:**

1. `formatReportSimple` transforms the report into an HTTP request that your API understands
2. [`sendRequester.SendReport()`](/cre/reference/sdk/http-client-go#sendrequestersendreport) calls your transformation function and sends the request
3. The SDK handles consensus and returns the result

The rest of this guide explains how this works and shows different formatting patterns for various API requirements.

## How it works

### The report structure

After [consensus](/cre/key-terms#consensus), [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values) returns a `sdk.ReportResponse`: the wire-format view of a [CRE report](/cre/key-terms#report-cre-report). It contains:

```go
type sdk.ReportResponse struct {
    RawReport     []byte                    // Your ABI-encoded data + metadata
    ReportContext []byte                    // Workflow execution context
    Sigs          []*AttributedSignature    // Cryptographic signatures from DON nodes
    ConfigDigest  []byte                    // DON configuration identifier
    SeqNr         uint64                    // Sequence number
}
```

This structure contains everything your API might need:

- **`RawReport`**: The actual report data (always required)
- **`Sigs`**: Cryptographic signatures from DON nodes (for verification)
- **`ReportContext`**: Metadata about the workflow execution
- **`SeqNr`**: Sequence number

### The transformation function

Your transformation function tells the SDK how to format the report for your API:

```go
func(reportResponse *sdk.ReportResponse) (*http.Request, error)
```

**The SDK calls this function internally:**

1. You pass your transformation function to [`SendReport()`](/cre/reference/sdk/http-client-go#sendrequestersendreport)
2. The SDK calls it with the generated `sdk.ReportResponse`
3. Your function returns an `http.Request` formatted for your API
4. The SDK sends the request and handles consensus

**Why is this needed?** Different APIs expect different formats:

- Some want raw binary data
- Some want JSON with base64-encoded fields
- Some want signatures in headers, others in the body

The transformation function gives you complete control over the format.

## Formatting patterns

Here are common patterns for formatting reports. Choose the one that matches your API's requirements.

### Choosing the right pattern

| Pattern                                                                                                 | When to use                                               |
| ------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- |
| [**Pattern 1: Report in body**](#pattern-1-report-in-body-simplest)                                     | Your API accepts raw binary data and handles decoding     |
| [**Pattern 2: Report + signatures in body**](#pattern-2-report--signatures-in-body)                     | Your API needs everything concatenated in one binary blob |
| [**Pattern 3: Report in body, signatures in headers**](#pattern-3-report-in-body-signatures-in-headers) | Your API needs signatures separated for easier parsing    |
| [**Pattern 4: JSON-formatted report**](#pattern-4-json-formatted-report)                                | Your API only accepts JSON payloads                       |

### Pattern 1: Report in body (simplest)

Use this when your API accepts raw binary data:

```go
import "google.golang.org/protobuf/types/known/durationpb"
import "time"

func formatReportSimple(r *sdk.ReportResponse) (*http.Request, error) {
    return &http.Request{
        Url:    "https://api.example.com/reports",
        Method: "POST",
        Body:   r.RawReport,  // Just send the report
        Headers: map[string]string{
            "Content-Type": "application/octet-stream",
        },
        CacheSettings: &http.CacheSettings{
            Store:  true,                             // Enable caching
            MaxAge: durationpb.New(60 * time.Second), // Accept cached responses up to 60 seconds old
        },
    }, nil
}
```

### Pattern 2: Report + signatures in body

Use this when your API needs everything concatenated in one payload:

```go
func formatReportWithSignatures(r *sdk.ReportResponse) (*http.Request, error) {
    var body []byte

    // Append the raw report
    body = append(body, r.RawReport...)

    // Append the context
    body = append(body, r.ReportContext...)

    // Append all signatures
    for _, sig := range r.Sigs {
        body = append(body, sig.Signature...)
    }

    return &http.Request{
        Url:    "https://api.example.com/reports",
        Method: "POST",
        Body:   body,
        Headers: map[string]string{
            "Content-Type": "application/octet-stream",
        },
        CacheSettings: &http.CacheSettings{
            Store:  true,                             // Enable caching
            MaxAge: durationpb.New(60 * time.Second), // Accept cached responses up to 60 seconds old
        },
    }, nil
}
```

### Pattern 3: Report in body, signatures in headers

Use this when your API needs signatures separated for easier parsing:

```go
import "encoding/base64"

func formatReportWithHeaderSigs(r *sdk.ReportResponse) (*http.Request, error) {
    headers := make(map[string]string)
    headers["Content-Type"] = "application/octet-stream"

    // Add signatures to headers
    for i, sig := range r.Sigs {
        sigKey := fmt.Sprintf("X-Signature-%d", i)
        signerKey := fmt.Sprintf("X-Signer-ID-%d", i)

        headers[sigKey] = base64.StdEncoding.EncodeToString(sig.Signature)
        headers[signerKey] = fmt.Sprintf("%d", sig.SignerId)
    }

    return &http.Request{
        Url:     "https://api.example.com/reports",
        Method:  "POST",
        Body:    r.RawReport,
        Headers: headers,
        CacheSettings: &http.CacheSettings{
            Store:  true,
            MaxAge: durationpb.New(60 * time.Second),
        },
    }, nil
}
```

### Pattern 4: JSON-formatted report

Use this when your API only accepts JSON payloads:

```go
import "encoding/json"

type ReportPayload struct {
    Report          string   `json:"report"`
    ReportContext   string   `json:"context"`
    Signatures      []string `json:"signatures"`
    ConfigDigest    string   `json:"configDigest"`
    SequenceNumber  uint64   `json:"seqNr"`
}

func formatReportAsJSON(r *sdk.ReportResponse) (*http.Request, error) {
    // Extract signatures
    sigs := make([]string, len(r.Sigs))
    for i, sig := range r.Sigs {
        sigs[i] = base64.StdEncoding.EncodeToString(sig.Signature)
    }

    // Create JSON payload
    payload := ReportPayload{
        Report:         base64.StdEncoding.EncodeToString(r.RawReport),
        ReportContext:  base64.StdEncoding.EncodeToString(r.ReportContext),
        Signatures:     sigs,
        ConfigDigest:   base64.StdEncoding.EncodeToString(r.ConfigDigest),
        SequenceNumber: r.SeqNr,
    }

    body, err := json.Marshal(payload)
    if err != nil {
        return nil, fmt.Errorf("failed to marshal report: %w", err)
    }

    return &http.Request{
        Url:    "https://api.example.com/reports",
        Method: "POST",
        Body:   body,
        Headers: map[string]string{
            "Content-Type": "application/json",
        },
        CacheSettings: &http.CacheSettings{
            Store:  true,
            MaxAge: durationpb.New(60 * time.Second),
        },
    }, nil
}
```

### Pattern 4 for offchain verification (hex)

Use when testing [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-go). The verify examples decode **hex without `0x`** in JSON. Base64 Pattern 4 above does not match unless you change the receiver.

```go
import (
	"encoding/hex"
	"encoding/json"
)

type ReportPayload struct {
	Report     string   `json:"report"`
	Context    string   `json:"context"`
	Signatures []string `json:"signatures"`
}

func formatReportAsJSONHex(config *Config) func(r *sdk.ReportResponse) (*http.Request, error) {
	return func(r *sdk.ReportResponse) (*http.Request, error) {
		sigs := make([]string, len(r.Sigs))
		for i, sig := range r.Sigs {
			sigs[i] = hex.EncodeToString(sig.Signature)
		}
		payload := ReportPayload{
			Report:     hex.EncodeToString(r.RawReport),
			Context:    hex.EncodeToString(r.ReportContext),
			Signatures: sigs,
		}
		body, err := json.Marshal(payload)
		if err != nil {
			return nil, err
		}
		return &http.Request{
			Url:    config.ApiUrl,
			Method: "POST",
			Body:   body,
			Headers: map[string]string{"Content-Type": "application/json"},
			CacheSettings: &http.CacheSettings{
				Store:  true,
				MaxAge: durationpb.New(60 * time.Second),
			},
		}, nil
	}
}
```

### Understanding `CacheSettings` for reports

You'll notice that all the patterns above include `CacheSettings`. This is critical for report submissions, just like it is for [POST requests](/cre/guides/workflow/using-http-client/post-request).

For a complete explanation of how `CacheSettings` works in general, see [Understanding `CacheSettings` behavior](/cre/reference/sdk/http-client#understanding-cachesettings-behavior) in the HTTP Client reference.

**Why use `CacheSettings`?**

When a workflow executes, **all nodes in the DON** attempt to send the report to your API. Without caching, your API would receive multiple identical submissions (one from each node). `CacheSettings` prevents this by having the first node cache the response, which other nodes can reuse.

**Why are cache hits limited for reports?**

Unlike regular POST requests where caching can be very effective, **reports have a more limited cache effectiveness** due to signature variance:

1. Each DON node generates its own **unique cryptographic signature** for the report
2. These signatures are part of the `sdk.ReportResponse` structure
3. When nodes construct the HTTP request body (whether concatenating signatures or including them in headers), the signatures differ

**In practice:** Even though cache hits are limited, you should still include `CacheSettings` to prevent worst-case scenarios where all nodes hit your API simultaneously.

**The real solution: API-side deduplication**

Because caching alone cannot prevent all duplicate submissions, your receiving API **must implement its own deduplication logic**:

- Use the **hash of the report** (`keccak256(RawReport)`) as the unique identifier
- Store this hash when processing a report
- Reject any subsequent submissions with the same hash

This approach is reliable because the `RawReport` is identical across all nodes—only the signatures vary.

## Using `SendReport` (recommended approach)

Use the high-level `http.SendRequest` pattern with [`sendRequester.SendReport()`](/cre/reference/sdk/http-client-go#sendrequestersendreport):

```go
func submitReportViaHTTP(config *Config, logger *slog.Logger, sendRequester *http.SendRequester) (*SubmitResponse, error) {
    // Assume 'report' was generated earlier in your workflow
    resp, err := sendRequester.SendReport(report, formatReportSimple).Await()
    if err != nil {
        return nil, fmt.Errorf("failed to send report: %w", err)
    }

    if resp.StatusCode != 200 {
        return nil, fmt.Errorf("API returned error: status=%d", resp.StatusCode)
    }

    logger.Info("Report submitted successfully", "statusCode", resp.StatusCode)
    return &SubmitResponse{Success: true}, nil
}

// In your trigger callback
func onCronTrigger(config *Config, runtime cre.Runtime, trigger *cron.Payload) (*MyResult, error) {
    logger := runtime.Logger()
    client := &http.Client{}

    // Call the submission function
    submitPromise := http.SendRequest(config, runtime, client,
        submitReportViaHTTP,
        cre.ConsensusIdenticalAggregation[*SubmitResponse](),
    )

    result, err := submitPromise.Await()
    if err != nil {
        return nil, err
    }

    return &MyResult{}, nil
}
```

## Complete working example

This example shows a workflow that:

1. Generates a report from a single value
2. Submits it via **Pattern 4 JSON with hex fields** (compatible with the verify guide sim loop)
3. Uses `config.ApiUrl` from your target config

```go
//go:build wasip1

package main

import (
	"encoding/hex"
	"encoding/json"
	"fmt"
	"log/slog"
	"math/big"
	"time"

	"github.com/ethereum/go-ethereum/accounts/abi"
	"github.com/smartcontractkit/chainlink-protos/cre/go/sdk"
	"github.com/smartcontractkit/cre-sdk-go/capabilities/networking/http"
	"github.com/smartcontractkit/cre-sdk-go/capabilities/scheduler/cron"
	"github.com/smartcontractkit/cre-sdk-go/cre"
	"github.com/smartcontractkit/cre-sdk-go/cre/wasm"
	"google.golang.org/protobuf/types/known/durationpb"
)

type Config struct {
	ApiUrl   string `json:"apiUrl"`
	Schedule string `json:"schedule"`
}

type SubmitResponse struct {
	Success bool
}

type MyResult struct{}

func InitWorkflow(config *Config, logger *slog.Logger, secretsProvider cre.SecretsProvider) (cre.Workflow[*Config], error) {
	return cre.Workflow[*Config]{
		cre.Handler(cron.Trigger(&cron.Config{Schedule: config.Schedule}), onCronTrigger),
	}, nil
}

type reportPayload struct {
	Report     string   `json:"report"`
	Context    string   `json:"context"`
	Signatures []string `json:"signatures"`
}

func formatReportForMyAPI(config *Config) func(r *sdk.ReportResponse) (*http.Request, error) {
	return func(r *sdk.ReportResponse) (*http.Request, error) {
		sigs := make([]string, len(r.Sigs))
		for i, sig := range r.Sigs {
			sigs[i] = hex.EncodeToString(sig.Signature)
		}
		body, err := json.Marshal(reportPayload{
			Report:     hex.EncodeToString(r.RawReport),
			Context:    hex.EncodeToString(r.ReportContext),
			Signatures: sigs,
		})
		if err != nil {
			return nil, err
		}
		return &http.Request{
			Url:    config.ApiUrl,
			Method: "POST",
			Body:   body,
			Headers: map[string]string{
				"Content-Type":   "application/json",
				"X-Report-SeqNr": fmt.Sprintf("%d", r.SeqNr),
			},
			CacheSettings: &http.CacheSettings{
				Store:  true,
				MaxAge: durationpb.New(60 * time.Second),
			},
		}, nil
	}
}

// Function that submits the report via HTTP
func submitReportViaHTTP(config *Config, logger *slog.Logger, sendRequester *http.SendRequester, report *cre.Report) (*SubmitResponse, error) {
	logger.Info("Submitting report to API", "url", config.ApiUrl)

	resp, err := sendRequester.SendReport(*report, formatReportForMyAPI(config)).Await()
	if err != nil {
		return nil, fmt.Errorf("failed to send report: %w", err)
	}

	logger.Info("Report submitted",
		"statusCode", resp.StatusCode,
		"bodyLength", len(resp.Body),
	)

	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("API error: status=%d, body=%s", resp.StatusCode, string(resp.Body))
	}

	return &SubmitResponse{Success: true}, nil
}

func onCronTrigger(config *Config, runtime cre.Runtime, trigger *cron.Payload) (*MyResult, error) {
	logger := runtime.Logger()

	// Step 1: Generate a report (example: a single uint256 value)
	myValue := big.NewInt(123456789)
	logger.Info("Generating report", "value", myValue.String())

	// ABI-encode the value
	uint256Type, err := abi.NewType("uint256", "", nil)
	if err != nil {
		return nil, fmt.Errorf("failed to create type: %w", err)
	}

	args := abi.Arguments{{Type: uint256Type}}
	encodedValue, err := args.Pack(myValue)
	if err != nil {
		return nil, fmt.Errorf("failed to encode value: %w", err)
	}

	// Generate the report
	reportPromise := runtime.GenerateReport(&cre.ReportRequest{
		EncodedPayload: encodedValue,
		EncoderName:    "evm",
		SigningAlgo:    "ecdsa",
		HashingAlgo:    "keccak256",
	})

	report, err := reportPromise.Await()
	if err != nil {
		return nil, fmt.Errorf("failed to generate report: %w", err)
	}
	logger.Info("Report generated successfully")

	// Step 2: Submit the report via HTTP
	client := &http.Client{}

	submitPromise := http.SendRequest(config, runtime, client,
		func(config *Config, logger *slog.Logger, sendRequester *http.SendRequester) (*SubmitResponse, error) {
			return submitReportViaHTTP(config, logger, sendRequester, report)
		},
		cre.ConsensusIdenticalAggregation[*SubmitResponse](),
	)

	submitResult, err := submitPromise.Await()
	if err != nil {
		logger.Error("Failed to submit report", "error", err)
		return nil, err
	}

	logger.Info("Workflow completed successfully", "submitted", submitResult.Success)
	return &MyResult{}, nil
}

func main() {
	wasm.NewRunner(cre.ParseJSON[Config]).Run(InitWorkflow)
}
```

### Configuration file (`config.json`)

```json
{
  "apiUrl": "https://webhook.site/your-unique-id",
  "schedule": "0 * * * *"
}
```

### Testing with webhook.site

1. Go to [webhook.site](https://webhook.site/) and get a unique URL
2. Update `config.json` with your webhook URL
3. Run the simulation:
   ```bash
   cre workflow simulate my-workflow --target staging-settings
   ```
4. On webhook.site, open **Content** and confirm JSON with `report`, `context`, and `signatures` (hex). Use that JSON to test a receiver workflow in [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-go#testing-locally-with-simulation).

## Next step: verify on the receiver

The sender does not validate the report for the receiver. After submission, the ingesting side must verify signatures before trusting the payload. See [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-go).

## Advanced: Low-level pattern

For complex scenarios where you need more control, use `client.SendReport()` with `cre.RunInNodeMode`:

```go
func onCronTrigger(config *Config, runtime cre.Runtime, trigger *cron.Payload) (*MyResult, error) {
    logger := runtime.Logger()

    // Assume 'report' was generated earlier

    submitPromise := cre.RunInNodeMode(config, runtime,
        func(config *Config, nodeRuntime cre.NodeRuntime) (*SubmitResponse, error) {
            client := &http.Client{}

            resp, err := client.SendReport(nodeRuntime, *report, formatReportSimple).Await()
            if err != nil {
                return nil, fmt.Errorf("failed to send report: %w", err)
            }

            if resp.StatusCode != 200 {
                return nil, fmt.Errorf("API error: %d", resp.StatusCode)
            }

            return &SubmitResponse{Success: true}, nil
        },
        cre.ConsensusIdenticalAggregation[*SubmitResponse](),
    )

    result, err := submitPromise.Await()
    if err != nil {
        return nil, err
    }

    return &MyResult{}, nil
}
```

## Best practices

1. **Always use `CacheSettings`**: Include caching in every transformation function to prevent worst-case duplicate submission scenarios
2. **Implement API-side deduplication**: Your receiving API must implement deduplication using the **hash of the report** (`keccak256(RawReport)`) to detect and reject duplicate submissions
3. **Verify on the receiver**: The sender does not validate the report; your API or a [receiver CRE workflow](/cre/guides/workflow/using-http-client/verifying-reports-offchain-go) must verify before trusting payload data
4. **Match your API's format exactly**: Study your API's documentation to understand the expected format (binary, JSON, headers, etc.)
5. **Handle errors gracefully**: Check HTTP status codes and provide meaningful error messages

> **CAUTION: Signature verification is your responsibility**
>
> Unlike onchain submissions (where the `KeystoneForwarder` contract verifies signatures), **HTTP submissions require you to verify signatures** before trusting the report data. Use [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) in a receiver workflow or implement equivalent checks in your API. See [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-go).

## Troubleshooting

**"failed to send report" error**

- Verify your API URL is correct and accessible
- Check that your transformation function returns a valid `http.Request`
- Ensure your API can handle binary data if you're sending raw bytes

**API returns 400/422 errors**

- Your report format likely doesn't match what your API expects
- Check if your API expects base64 encoding, JSON wrapping, or specific headers

## Learn more

- **[Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-go):** verify signatures on the receiver before trusting payload data
- **[HTTP Client SDK Reference](/cre/reference/sdk/http-client):** complete API reference including `SendReport` and `sdk.ReportResponse`
- **[POST Requests](/cre/guides/workflow/using-http-client/post-request):** HTTP request patterns and caching
- **[Generating Reports: Single Values](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values):** create reports from single values
- **[Generating Reports: Structs](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-structs):** create reports from struct data
- **[Submitting Reports Onchain](/cre/guides/workflow/using-evm-client/onchain-write/submitting-reports-onchain):** submit reports to smart contracts instead of HTTP