# Send Events

> Tell Revnu from your server when a visitor signs up, books a call or pays, so the result is tied back to where they came from.

Canonical: https://revnu.com/docs/event-api

The [Revnu Tag](/docs/revnu-tag) sees visits. The Event API is how your server tells Revnu something real happened: a signup, a booked call, a payment. Revnu joins the event to the visit that led to it, so you can see which source (an AI assistant, search, an ad) brought the customers who converted.

```bash
curl https://revnu.com/api/measurement/events \
  -H "Authorization: Bearer $REVNU_EVENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "event_id": "signup_8412",
    "event_name": "signup",
    "anonymous_id": "ra_3f9c0a1b2c3d4e5f60718293a4b5c6d7",
    "email": "ana@example.com"
  }'
```

## Your event key

The event key starts with `rsk_live_` and comes with your site key. Keep it in a server-side environment variable: anyone who has it can send events for your site. It is not the `rvp_` [personal API token](/docs/authentication).

## Connect the visit to the event

The tag gives every visitor an id. Read it in the browser and send it to your backend with the signup, booking or checkout:

```js
const anonymousId = window.revnu?.anonymousId; // "ra_..."
```

Then pass it as `anonymous_id` when your server sends the event. That id is what ties the event to the visit.

## Request

| Field | Required | Notes |
|---|---|---|
| `event_id` | yes | Your stable id for this event. 1-128 letters, digits, `.`, `_`, `:` or `-`. Sending the same id again is safe: it is reported as a duplicate, not counted twice. |
| `event_name` | yes | Lowercase snake_case, for example `signup`, `demo_booked`, `purchase`. |
| `occurred_at` | no | ISO 8601 date or milliseconds. Defaults to when Revnu receives it. |
| `anonymous_id` | no | `window.revnu.anonymousId` from the visit. |
| `email`, `phone`, `external_user_id` | no | Normalized and hashed for matching. |
| `amount`, `currency` | no | Send both, for example `49` and `"USD"`. |
| `properties` | no | An object of extra facts, for example `{ "plan": "pro" }`. |
| `reversal_of` | no | The `event_id` of an earlier event this one cancels, such as a refund. Repeat the original `amount` and `currency`. |

## Node.js

```ts
await fetch("https://revnu.com/api/measurement/events", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.REVNU_EVENT_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    event_id: `signup_${user.id}`,
    event_name: "signup",
    occurred_at: user.createdAt.toISOString(),
    anonymous_id: revnuAnonymousId, // sent up from the browser
    email: user.email,
  }),
});
```

## Response

```json
{
  "accepted": true,
  "matched": false,
  "duplicate": false,
  "enriched": false,
  "event_id": "signup_8412",
  "event_name": "signup",
  "roles": [],
  "attribution_status": "pending"
}
```

| Code | Meaning |
|---|---|
| 200 | Recorded and tied to one of your Revnu ad campaigns, or a duplicate of an event already recorded |
| 202 | Recorded. Nothing to fix; most events land here |
| 401 | Missing or invalid event key |
| 409 | The same `event_id` was sent before with different values |
| 413 | Body over 64 KB |
| 422 | A field failed validation; `error` says which |
| 429 | Rate limited; retry later with the same `event_id` |

## Frequently asked questions

### Can I send events from the browser?

No. The event key would be public. Send events from your backend, a serverless function or a webhook handler, and let the browser pass along only the `anonymous_id`.

### What happens if I send the same event twice?

Nothing is double counted. The response says `"duplicate": true`. Retrying a failed request with the same `event_id` and body is always safe.

### Should I send page views?

No. The Revnu Tag already records visits. Send the outcomes your server knows are real: signups, bookings, trials, payments.
