# Event Verification
Source: https://docs.chain.link/crec/concepts/event-verification
Last Updated: 2026-08-31

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

This page describes exactly what happens when you call the verification helpers on `client.Events` from the CRE Connect Go SDK. The algorithm is implemented in `events/events.go` (`crec-sdk`). Use this page as a conceptual reference, and see the [Verify Event Signatures](/crec/guides/events/verify-signatures) guide for runnable code.

## Entry points, one algorithm

The SDK exposes type-specific verifiers; each handles exactly one event family and uses the same underlying algorithm:

| Method                                       | Event type accepted | Returns when called on the wrong type |
| -------------------------------------------- | ------------------- | ------------------------------------- |
| `client.Events.Verify(event)`                | `watcher.event`     | `ErrOnlyWatcherEventsSupported`       |
| `client.Events.VerifyOperationStatus(event)` | `operation.status`  | `ErrOnlyOperationStatusSupported`     |
| `client.Events.VerifyQueryStatus(event)`     | `query.status`      | `ErrOnlyQueryStatusSupported`         |

Per-call variants (`VerifyWithOrgID`, `VerifyWithWorkflowOwner`, `VerifyOperationStatusWithOrgID`, `VerifyOperationStatusWithWorkflowOwner`, `VerifyQueryStatusWithOrgID`, `VerifyQueryStatusWithWorkflowOwner`) let you override the workflow-owner identity for a single call without rebuilding the client. The `watcher.status` and `wallet.status` event families are not covered by these helpers; for those, use the lower-level `VerifyOCRSignatures(...)` directly (see [Lower-level entry point](#lower-level-entry-point)).

## Inputs

Both verifiers require three things from the SDK configuration plus the event itself:

- **The event** with its `headers.proofs` populated (exactly one `OCRProof`).
- **A workflow owner address.** The SDK derives this either from a configured `OrgID` (passed via `crec.WithOrgID(...)`) or directly from a configured `WorkflowOwner` (`crec.WithWorkflowOwner(...)`). There is no default: at least one of the two must be set.
- **A signer set.** The SDK uses `DefaultValidSigners` unless overridden.
- **A signature threshold.** The SDK uses `DefaultMinRequiredSignatures = 4` unless overridden.

If neither `OrgID` nor `WorkflowOwner` is configured, both verifiers return the sentinel `ErrOrgIDOrWorkflowOwnerReq` on every call.

## Algorithm: step by step

### 1. Type and payload extraction

Each verifier first checks `event.Headers.Type` matches the type it accepts (and returns the corresponding sentinel above otherwise). It then extracts the typed payload, `WatcherEventPayload` for `Verify` and `OperationStatusPayload` for `VerifyOperationStatus`, which carries the `verifiable_event` bytes used in step 3.

### 2. Off-Chain Reporting (OCR) proof extraction

The proofs array must contain exactly one `OCRProof`. The SDK enforces this with two sentinels:

- `ErrNoOCRProofs`: empty proofs array.
- `ErrMultipleOCRProofs`: more than one proof present (other proof types are skipped, but multiple OCR proofs are an error).

`parseOCRProofData` then hex-decodes both `ocr_report` and `ocr_context` and validates that the report is at least `109 + 32 = 141` bytes long (the offset of the embedded payload hash).

### 3. Local event hash

The SDK computes the **local event hash** from the payload received in the envelope:

```text
eventHash = keccak256(payload.verifiable_event)
```

This is the application-visible payload, the bytes you would actually consume, turned into a 32-byte digest for comparison against the on-chain-attested hash.

> **NOTE: `query.status` uses a different hash input**
>
> For `query.status` events, the hash is computed over `payload.verifiable_result` instead of
> `payload.verifiable_event`. See [Chain Queries](/crec/concepts/queries).

### 4. Bind report, workflow owner, and event hash (`verifyEventHash`)

`verifyEventHash` enforces two invariants on the OCR report:

| Bytes                | Meaning                                                                 | Check                                                                             |
| -------------------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| `ocr_report[87:107]` | The 20-byte address of the **workflow owner** that produced the report. | Must equal the configured `workflowOwner` (derived from `OrgID` or set directly). |
| `ocr_report[109:]`   | The 32-byte hash the workflow signed over.                              | Must equal the locally-computed `eventHash`.                                      |

Either check failing aborts verification: the event was either signed by a different workflow or for different bytes than what the SDK observed.

### 5. OCR signature verification (`verifySignatures`)

Once the report is bound to the right workflow and payload, the SDK validates the OCR signatures:

```text
reportHash = keccak256(keccak256(ocr_report) ‖ ocr_context)
```

For each entry in `proof.signatures`:

1. Decode the hex signature into a 65-byte (`r ‖ s ‖ v`) buffer.
2. Normalize the `v` byte: Ethereum-style values (`27` / `28`) are decremented by `27` to match the `secp256k1` `0` / `1` convention.
3. Recover the public key with `crypto.SigToPub(reportHash, sig)`.
4. Convert the public key to an Ethereum address with `crypto.PubkeyToAddress`.
5. If that address is in the configured **valid signers map** *and* has not already been used in this round, increment the unique signature count.

Verification short-circuits as soon as the count reaches `MinRequiredSignatures` (default 4).

### 6. Decision

| Result                                                                       | Meaning                                                                                                                                                                                          |
| ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `(true, nil)`                                                                | All checks passed: the event is authentic and untampered.                                                                                                                                        |
| `(false, ErrInvalidEventHash)`                                               | Workflow-owner / event-hash binding (`verifyEventHash`) failed: bytes `87:107` did not equal the configured workflow owner, or bytes `109+` did not equal `keccak256(payload.verifiable_event)`. |
| `(false, ErrVerifyEvent)`                                                    | Lower-level error wrapping the underlying cause (malformed report, bad signature length, signer recovery failure, etc.).                                                                         |
| `(false, ErrVerificationNotConfigured)`                                      | The valid-signers map is empty (configure `crec.WithEventVerification`).                                                                                                                         |
| `(false, ErrOrgIDOrWorkflowOwnerReq)` / `ErrWorkflowOwnerRequired`           | Verifier identity context not configured.                                                                                                                                                        |
| `(false, ErrOnlyWatcherEventsSupported)` / `ErrOnlyOperationStatusSupported` | Wrong helper called for the event type.                                                                                                                                                          |
| `(false, nil)`                                                               | Signatures parsed cleanly but fewer than `MinRequiredSignatures` recovered to known signers.                                                                                                     |

The full sentinel list is documented in [Error Handling](/crec/reference/error-handling).

## Worked example

Below is the conceptual flow when an application receives a `watcher.event` and verifies it. Add the `events` import, `import "github.com/smartcontractkit/crec-sdk/events"`, to use the sentinel errors.

```go
polled, _, err := client.Events.Poll(ctx, channelID, nil)
if err != nil { /* handle */ }

for _, ev := range polled {
    ok, err := client.Events.Verify(&ev)
    switch {
    case errors.Is(err, events.ErrNoOCRProofs):
        // The event arrived before its OCR proof: re-poll later.
        continue
    case err != nil:
        log.Printf("verification error: %v", err)
        continue
    case !ok:
        log.Printf("event %s failed verification", ev.EventId)
        continue
    }
    handle(ev)
}
```

`Verify` and `VerifyOperationStatus` are purely local: they do no network I/O. `Poll` makes a single HTTP call to the events endpoint and surfaces transport errors to the caller without retry, so wrap it in your own retry logic if you need automatic retries. When verification returns `ErrNoOCRProofs` for an event, skip that event with `errors.Is(err, events.ErrNoOCRProofs)` and re-poll on the next cycle.

## Lower-level entry point

If you only have an OCR report, an OCR context, and a list of signatures (for example, when bridging events out of one CRE Connect tenant and verifying them in another system), call `VerifyOCRSignatures(ocrReport, ocrContext, signatures)`. This entry point performs steps 5–6 above without the workflow-owner / event-hash binding from step 4.

> **CAUTION: Workflow-owner binding is essential**
>
> The workflow-owner check is what proves the event was generated by *your tenant's* workflows, not just by *some* CRE
> workflow. Skipping it (by calling `VerifyOCRSignatures` instead of `Verify`) is appropriate only when you have already
> established workflow-owner trust through another channel.

## Related

- [Verifiable Events](/crec/concepts/verifiable-events): what `Verify` actually proves.
- [Verify Event Signatures](/crec/guides/events/verify-signatures): the runnable SDK recipe.
- [SDK Configuration Options](/crec/reference/sdk-configuration): `WithEventVerification`, `WithOrgID`, `WithWorkflowOwner`.
- [Error Handling](/crec/reference/error-handling#events): the verification-related sentinel errors.