# Securing Mechanic webhooks

[Mechanic webhooks](/platform/webhooks.md) are configured properly for CORS, which makes them suitable for submissions from your online store's frontend.

These webhooks do not include any authentication, so you may want to add some verification in your own implementation.

### Generating signatures

By establishing a shared secret value, stored in server-side Shopify theme code and in server-side Mechanic task code, the webhook request data can be "signed" with a signature value, uniquely determined by the shared secret combined with the request data. This prevents would-be attackers from modifying the webhook data and re-using the same signature, because any modification to the webhook data would invalidate the signature.

One might use something like this in the Shopify theme code:

```markup
<script>
  $.post(
    'https://usemechanic.com/webhook/000000000',
    {
      customer_id: {{ customer.id | json }},
      customer_id_signature: {{ customer.id | hmac_sha256: "some-secret-value" | json }}
    }
  );
</script>
```

... and then something like this, in the corresponding Mechanic task code:

```liquid
{% assign customer_id = event.data.customer_id %}
{% assign customer_id_signature = event.data.customer_id_signature %}

{% assign expected_customer_id_signature = customer_id | hmac_sha256: "some-secret-value" %}

{% if expected_customer_id_signature != customer_id_signature %}
  {% error "Customer ID signature did not match." %}
{% endif %}
```

### Preventing replay attacks

The approach above prevents would-be attackers from passing off altered data as legitimate, but it does not prevent manually repeated submissions of the *same* data.

#### Using idempotent task code

The best approach for preventing this is to ensure that the Mechanic task code is fully idempotent, such that it only performs the desired work *once*, no matter how many times it is invoked.

One way to solve this is to leverage [the Mechanic cache](/platform/cache.md) to remember that a given request has already been processed. Here's an example:

```liquid
{% assign cache_key = event.data | json | sha256 %}

{% if cache[cache_key] %}
  {% log "we've already seen this request; skipping it" %}
  {% break %}
{% endif %}

{% action "cache", "set", cache_key, true %}

[... proceed with processing the event]
```

In this way, later submissions of the same webhook data will be ignored.

#### Adding time to the request signature

To prevent a replay of the same request *much* later, add a rounded representation of the current time to the signature calculation. Here's an example, on the theme code side:

```liquid
{% assign time_rounded = "now" | date: "%s" | times: 1.0 | divided_by: 60 | round %}
{% assign signature_data = customer.id | append: time_rounded %}
{% assign signature = hmac_sha256: "some-secret-value" %}
```

And here's how this might be validated in task code:

```liquid
{% assign customer_id = event.data.customer_id %}
{% assign customer_id_signature = event.data.customer_id_signature %}

{% assign time_rounded = "now" | date: "%s" | times: 1.0 | divided_by: 60 | round %}
{% assign expected_customer_id_signature = customer_id | append: time_rounded | hmac_sha256: "some-secret-value" %}

{% if expected_customer_id_signature != customer_id_signature %}
  {% error "Customer ID signature did not match." %}
{% endif %}
```

In this way, the signature will only be valid within a 60-minute interval.

Bear in mind, with this strategy, timeliness becomes important! A [queue delay](/faq/why-are-my-tasks-delayed-or-not-running.md) could push the event past the rounding, resulting in a new time value, and invalidating the request signature.


---

# 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://learn.mechanic.dev/techniques/securing-mechanic-webhooks.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.
