# The generic contract

> The shape to send when no adapter fits your stack.

This is the shape to send when no adapter fits your stack. Both keyed endpoints read it, and a webhook can carry it too.

## The two settings

| | |
|---|---|
| secret | Optional. An HMAC-SHA256 over the raw body, for the webhook path. With no secret set, the token in the URL is the only guard. |
| trust_sender_attribution | Default true. Set it false to ignore human-agent and platform-injected claims from the sender, so every turn your deployment produced is treated as the agent. |

With a secret set, send the digest as hex in an x-signature header. An x-hub-signature-256 header works too, and anything before an equals sign is ignored.

## The event shape

One event is one JSON object. Send one, an array of them, or an object with an events array. Bulk import also reads one record per line.

```json
{
    "thread_id": "contact-4812",
    "session_id": "call-99f1",
    "type": "message.sent",
    "ts": 1755820800000,
    "payload": {
        "role": "assistant",
        "text": "Your order ships tomorrow."
    },
    "source": {
        "platform_msg_id": "msg-7731",
        "agent_version": "support-agent-3.2"
    }
}
```

| | |
|---|---|
| thread_id | Required. The conversation this belongs to, as your own system keys it. |
| session_id | Optional. One call or one chat inside that conversation. |
| type | Required. One of message.received, message.sent, tool.called, tool.result, call.started, call.ended, session.note. |
| ts | Required. Epoch milliseconds of when the message was created, never when you sent it to us. A value near 1.7e9 is seconds, and we refuse it. |
| source.platform_msg_id | Required. Your own id for this one event. |
| source.agent_version | Optional. Which build of the agent produced it. |
| payload.role | user, assistant or system. |
| payload.text | What was said. |
| payload.audio_ref | Where the recording is, for a voice turn. |
| payload.tool | For a tool event: name, call_id, arguments, result, error. |

A timestamp must fall between 2015 and 2100. An id is a string of at most 512 characters. One payload carries at most 5000 events.

> **Note:** Do not set ts_precision or clock. The adapter fixes both, at milliseconds and a clock of its own, and an event that sets either one is refused.

## What we do with sender attribution

Every turn your deployment produced is tagged, so a reply a person typed is never treated as the agent. An assistant turn is tagged as the agent, a system turn as platform injected, and a user turn is left alone.

You can say so per event with payload.source_tag, set to agent, platform_injected or human_agent. On a human_agent turn you can also send payload.operator_external_id and payload.operator_display_name, which name the person who replied. Send a work identity there, a staff email or a console user id, never a customer's own details.

Set trust_sender_attribution to false where you do not control the sender. A claim that excuses the agent, human_agent or platform_injected, is then dropped and the default stands. A claim that blames the agent is kept either way.

---

Source: https://evidova.com/docs/api/generic
