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

# Verifying a Signature

> Validate the clickwrap Signature on your backend to verify the event and confirm end user consent.

<Note>
  **Prerequisites:** A completed [SDK integration](/dev/guides/displaying-clickwrap) that returns a Signature, and your [App ID + App Key](/product/integrations/integrations-overview) for server-side calls.
</Note>

After your end user accepts or declines a clickwrap, the SDK creates an event in ClickTerm and returns a **Signature**. The event remains unverified until your backend sends this Signature to ClickTerm's verification endpoint. Verification confirms the Signature hasn't been forged or tampered with and marks the event as verified. For accepted events, a **Certificate of Acceptance** is generated.

**Why it matters:** Verification ensures the Signature hasn't been forged or tampered with, providing proof of consent, security, and legal validity for clickwrap agreements.

<Warning>
  Never expose your **App Key** in client-side code. The App Key is used
  only for backend verification calls. The client SDK uses only the **App ID**.
  Store the App Key safely — it won't display again after creation, but can be
  regenerated. Regenerating the key requires updating your backend configuration.
</Warning>

## Endpoint

```
POST https://api.clickterm.com/public-client/v1/clickwrap/verify
```

## Request

<CodeGroup>
  ```java example.java (Java) theme={null}
  HttpResponse<String> response = Unirest.post(
      "https://api.clickterm.com/public-client/v1/clickwrap/verify")
    .header("X-APP-ID", "YOUR_APP_ID")
    .header("X-APP-KEY", "YOUR_APP_KEY")
    .header("Content-Type", "application/json")
    .body("{\"clicktermSignature\":\"SIGNATURE_FROM_SDK\"}")
    .asString();
  ```

  ```javascript example.js (Node.js) theme={null}
  const response = await fetch(
    "https://api.clickterm.com/public-client/v1/clickwrap/verify",
    {
      method: "POST",
      headers: {
        "X-APP-ID": process.env.CLICKTERM_APP_ID,
        "X-APP-KEY": process.env.CLICKTERM_APP_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        clicktermSignature: signatureFromSdk,
      }),
    }
  );
  const event = await response.json();
  ```

  ```python example.py (Python) theme={null}
  import requests

  response = requests.post(
      "https://api.clickterm.com/public-client/v1/clickwrap/verify",
      headers={
          "X-APP-ID": CLICKTERM_APP_ID,
          "X-APP-KEY": CLICKTERM_APP_KEY,
          "Content-Type": "application/json",
      },
      json={"clicktermSignature": signature_from_sdk},
  )
  event = response.json()
  ```
</CodeGroup>

### Request body

| Parameter            | Required | Description                                               |
| -------------------- | -------- | --------------------------------------------------------- |
| `clicktermSignature` | Yes      | The Signature returned by the SDK after the user's action |

## Response

```json theme={null}
{
  "clickwrapEventStatus": "ACCEPTED",
  "clickwrapEventId": "123e4567-e89b-12d3-a456-426614174000",
  "clickwrapTemplateId": "987e6543-e21b-45d3-b321-426614174999",
  "clickwrapTemplateVersion": 1,
  "clickwrapTemplateVersionMinor": 0,
  "endUserId": "user-123",
  "templatePlaceholders": "{\"email\":\"user@example.com\"}",
  "technicalMetadata": "{\"userAgent\":\"Mozilla/5.0...\",\"ip\":\"203.0.113.42\"}",
  "actionAt": "2026-03-23T14:30:00Z",
  "effectiveAt": "2026-03-23T14:00:00Z"
}
```

### Response fields

| Field                           | Type      | Description                                                 |
| ------------------------------- | --------- | ----------------------------------------------------------- |
| `clickwrapEventStatus`          | String    | `ACCEPTED` or `DECLINED`                                    |
| `clickwrapEventId`              | UUID      | Unique identifier for this event                            |
| `clickwrapTemplateId`           | UUID      | The template shown to the end user                          |
| `clickwrapTemplateVersion`      | Integer   | Major version number                                        |
| `clickwrapTemplateVersionMinor` | Integer   | Minor version number                                        |
| `endUserId`                     | String    | The end user identifier you provided                        |
| `templatePlaceholders`          | String    | JSON-encoded placeholder values (or null)                   |
| `technicalMetadata`             | String    | IP address, user agent, and other client metadata           |
| `actionAt`                      | Timestamp | When the user's action occurred (UTC, ISO-8601)             |
| `effectiveAt`                   | Timestamp | When this template version became effective (UTC, ISO-8601) |

## Framework examples

<CodeGroup>
  ```java example.java (Spring Boot) theme={null}
  @RestController
  @RequestMapping("/api")
  public class ConsentController {

      @Value("${clickterm.app-id}")
      private String appId;

      @Value("${clickterm.app-key}")
      private String appKey;

      @PostMapping("/verify-consent")
      public ResponseEntity<?> verifyConsent(@RequestBody Map<String, String> body) {
          HttpResponse<String> response = Unirest.post(
              "https://api.clickterm.com/public-client/v1/clickwrap/verify")
            .header("X-APP-ID", appId)
            .header("X-APP-KEY", appKey)
            .header("Content-Type", "application/json")
            .body(Map.of("clicktermSignature", body.get("clicktermSignature")))
            .asString();

          // Parse and handle the response
          return ResponseEntity.ok(response.getBody());
      }
  }
  ```

  ```javascript example.js (Express) theme={null}
  const express = require("express");
  const router = express.Router();

  router.post("/verify-consent", async (req, res) => {
    const { clicktermSignature } = req.body;

    const response = await fetch(
      "https://api.clickterm.com/public-client/v1/clickwrap/verify",
      {
        method: "POST",
        headers: {
          "X-APP-ID": process.env.CLICKTERM_APP_ID,
          "X-APP-KEY": process.env.CLICKTERM_APP_KEY,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ clicktermSignature }),
      }
    );

    const event = await response.json();

    if (event.clickwrapEventStatus === "ACCEPTED") {
      // Proceed with registration or flow
      res.json({ success: true, eventId: event.clickwrapEventId });
    } else {
      res.status(403).json({ success: false, status: event.clickwrapEventStatus });
    }
  });

  module.exports = router;
  ```

  ```python example.py (Django) theme={null}
  import requests
  from django.http import JsonResponse
  from django.views.decorators.http import require_POST
  from django.conf import settings

  @require_POST
  def verify_consent(request):
      import json
      body = json.loads(request.body)

      response = requests.post(
          "https://api.clickterm.com/public-client/v1/clickwrap/verify",
          headers={
              "X-APP-ID": settings.CLICKTERM_APP_ID,
              "X-APP-KEY": settings.CLICKTERM_APP_KEY,
              "Content-Type": "application/json",
          },
          json={"clicktermSignature": body["clicktermSignature"]},
      )
      event = response.json()

      if event["clickwrapEventStatus"] == "ACCEPTED":
          return JsonResponse({"success": True, "eventId": event["clickwrapEventId"]})
      else:
          return JsonResponse({"success": False, "status": event["clickwrapEventStatus"]}, status=403)
  ```
</CodeGroup>

<Warning>
  Requests to `/clickwrap/verify` **are counted toward billing**. Implement rate
  limiting or a Captcha check before this step to prevent abuse from end users.
</Warning>

<Note>
  Until verification is performed, the Clickwrap Event will be visible on the ClickTerm Dashboard but will remain in an **Unverified** state.
</Note>

<Note>
  If a user declines the agreement, the event is still verified but no Certificate of Acceptance is generated. It is up to your application to decide how to handle this (e.g., blocking the user journey, restricting features, or allowing continued access).
</Note>

## Next steps

<CardGroup cols={2}>
  <Card title="Download certificates" icon="file-certificate" iconType="light" href="/dev/guides/downloading-certificates">
    Retrieve the Certificate of Acceptance PDF.
  </Card>

  <Card title="Check consent status" icon="circle-check" iconType="light" href="/dev/guides/checking-consent-status">
    Query whether an end user has accepted the latest version.
  </Card>

  <Card title="API reference" icon="square-terminal" iconType="light" href="/api-reference/clickwrap/verify-clickwrap-signature">
    Full request/response details for the verify endpoint.
  </Card>
</CardGroup>
