> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.6mm.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.6mm.com/_mcp/server.

# Webhook Signature Verification & Idempotency

This page follows [Agent Java SDK `WEBHOOKS.md`](https://github.com/6mm-com/agent-java-sdk/blob/fa6c7b65124f1346f815b3b73eb8a7729466c3dc/WEBHOOKS.md) at commit `fa6c7b6`. This is a documentation-only update; no new SDK artifact containing warning helper methods was released.

All four event types use one merchant callback URL and are dispatched by the body `event`. See [Webhook Events & Liquidation Warnings](/sdk/agent-sdk/java/webhook-events) for event fields and payload examples.

## Webhook headers

| Header              | Description                        |
| ------------------- | ---------------------------------- |
| `X-Agent-Timestamp` | Unix timestamp in seconds.         |
| `X-Agent-Nonce`     | Nonce.                             |
| `X-Agent-Signature` | HMAC-SHA256 hexadecimal signature. |

```text
timestamp + nonce + rawBody
```

## Event idempotency

Persist deduplication within the merchant and `environment` scope, atomically with business processing. Different `notificationId` values identify independent `FIRST` / `UPGRADE` / `REMINDER` notifications; do not discard notifications by grouping on user, episode, or `eventType`. An empty idempotency key cannot be used for deduplication. The SDK only returns a helper key; it does not persist or process business data. Record the same identity with a different body as a conflict; do not overwrite the processed body.

## Signature verification and receiving

Use the merchant API secret to calculate the HMAC-SHA256 hexadecimal signature of `timestamp + nonce + rawBody`. Verify the original received bytes; do not format JSON, rename fields, or re-serialize the body first.

Verify the signature first, then check the time window (recommended ±300 seconds), merchant, and environment. Next extract the idempotency identity and handle the notification reliably. Preserve unknown event values for diagnosis; never interpret them as funds success. The verification helper does not perform time-window, ownership, or persistent deduplication checks.

Return HTTP 2xx after completing processing or confirming an identical duplicate; return non-2xx on failure. The warning sender has a 5-second HTTP timeout and at most 8 attempts. Retries retain the frozen body; only the signature timestamp and nonce change. Respond promptly after reliable acceptance; long-running business work can use the merchant's own persistent tasks.

Merchant notifications do not depend on the user's online status. Platform user pushes are online best-effort delivery only, with no offline replay; phase one has no notification inbox.

### Successful responses and stopping retries

Funds callbacks and liquidation warnings use **only the HTTP status code** to determine delivery success. The sender does not interpret response-body `code`, `success`, `result`, or `message`, and does not require a fixed `OK` / `success` response.

| Merchant response                                                       | Platform behavior                                                                                                                                                      |
| ----------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| HTTP 200–299, including 200, 202, and 204                               | Delivery succeeds; automatic resends stop after the success state is persisted.                                                                                        |
| Final non-2xx response, including 400, 401, 403, 409, 429, 500, and 503 | Delivery fails and retries follow the existing backoff policy; after the attempt limit it is marked `DEAD`. A 4xx does not automatically acknowledge the notification. |
| Connection failure or response timeout without a usable response        | The result is unconfirmed and retried under the failure policy.                                                                                                        |

The response body may be empty, text, or JSON. The example `result` fields are informational for the merchant, not protocol requirements. `202 Accepted` also stops platform retries, so return it only after reliably saving the notification to the merchant's persistent task queue, not merely starting an in-memory async task.

### Successful response examples

After business processing completes, or the notification has been reliably saved to the merchant's own pending queue:

```http
HTTP/1.1 200 OK
Content-Type: application/json

{"result":"RECEIVED"}
```

When no response body is needed:

```http
HTTP/1.1 204 No Content
```

For an already-processed notification with an identical body, skip duplicate business processing and still return 2xx:

```http
HTTP/1.1 200 OK
Content-Type: application/json

{"result":"DUPLICATE"}
```

### Failure response examples

If the database is temporarily unavailable and the merchant has neither completed processing nor reliably saved the notification, return non-2xx so the platform can retry:

```http
HTTP/1.1 503 Service Unavailable
Content-Type: application/json

{"result":"FAILED","message":"temporary storage failure"}
```

Reject an invalid signature without updating business data or successful deduplication records:

```http
HTTP/1.1 401 Unauthorized
Content-Type: application/json

{"result":"INVALID_SIGNATURE"}
```

These response bodies do not change retry decisions. Do not expect a 401 or 409 to permanently stop retries; investigate the signature, notification content, or merchant configuration.

**Incorrect example: the following response still counts as successful delivery. The platform will not retry because of `success:false`.**

```http
HTTP/1.1 200 OK
Content-Type: application/json

{"success":false,"code":500,"message":"processing failed"}
```

If the framework or global exception handler wraps business failures in HTTP 200, explicitly set the correct failure status at the callback endpoint.

### Reliable acknowledgement and duplicate delivery

Recommended order: verify the signature and check time / merchant / environment → extract the idempotency identity → process business data and successful deduplication records in a transaction, or reliably save a pending task → return 200/204 after the commit succeeds. Do not return 2xx or mark the notification processed before a successful database write.

Duplicate delivery can still occur after merchant processing succeeds: the response may be lost, or the platform may receive 2xx but fail to persist `SUCCESS`. HTTP callbacks do not guarantee one-time delivery. Persist deduplication within the merchant and `environment` scope. Funds use `orderType:orderId:targetStatus`; warnings use `liquidation.warning:notificationId`. For an identical already-processed body, return 2xx without crediting funds or issuing a reminder again. For the same identity with a different body, record a conflict and return non-2xx; do not overwrite the original record.

Acknowledgement success means the merchant received / processed the notification, not that the funds operation succeeded. After recording a valid funds notification with `targetStatus=FAILED`, also return 2xx; an order failure is not a reason to reject notification acknowledgement.

## Funding and environment boundaries

The original Contract transfer webhook and `queryOrder` flow do not automatically apply to Funding. Confirm Funding operations using `queryWalletTransfer` and the original order environment. Funding uses `status`; original Contract transfer responses use `orderStatus`. A successful HTTP response or `code=0` does not by itself prove funds moved.

Explicit simulated Contract transfer notifications include `environment=SIMULATED`. Verify the raw body first, then check the user, amount, order identity and environment against the saved local business order.

[Funding recovery guide](/sdk/agent-sdk/java/funding-wallet) · [Environment guide](/sdk/agent-sdk/java/environments)