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

# Send Events from Backend

> Send events that happen on your server, from Python or any other language

Some outcomes happen only on your server: an order completes, a refund is issued, a ticket is escalated. Send them with the same write key the [browser SDK](/docs/user-signals/browser-events#get-a-write-key) uses.

## From Python

`dataframer-journey`, the library from [Link AI Traces to Journeys](/docs/user-signals/link-traces), sends them. Inside a request, the event joins that request's journey.

```python theme={null}
from dataframer_journey import Signals

signals = Signals(write_key=os.environ['DATAFRAMER_WRITE_KEY'])
signals.track('OrderCompleted', {'order_id': order.id, 'total': 42.5}, user_id=user.email)
```

Events are sent in the background, in batches, and retried while DataFramer is busy or unreachable. An event the server keeps failing on is dropped after five tries, with a warning in your logs. Outside a request, pass `journey_id=` yourself. In a short-lived process such as a serverless function, call `signals.flush()` before it returns. It waits up to 30 seconds, or the `timeout=` you pass, and returns `False` if some events are still unsent. In a [customer-hosted deployment](/docs/deployment#user-signals-in-a-customer-hosted-deployment), also pass `api_base=`, the browser SDK's `apiBase`.

## From any other language

Post the events to the batch endpoint. In a customer-hosted deployment, it is under your data plane's address instead.

```bash theme={null}
curl -X POST https://df-api.dataframer.ai/api/signals/batch/ \
  -H 'Content-Type: application/json' \
  -d '{"writeKey": "YOUR_WRITE_KEY", "batch": [{"type": "track", "event": "OrderCompleted",
       "messageId": "order-1234-completed", "userId": "user@acme.com",
       "properties": {"total": 42.5}, "context": {"journey_id": "JOURNEY_ID"}}]}'
```

* A request carries up to 100 events and 1 MB. Each event's `properties` and `context` together may take up to 64 KB of JSON.
* The journey id goes in `context.journey_id`. Add `timestamp` (ISO 8601, 1970 or later) when the event happened before you send it.
* Give each event a unique `messageId`. An event sent again with the same one is stored once, so a retry is safe.

## Next steps

> **[Import Events from Warehouse](/docs/user-signals/warehouse-events)**
>
> Use the events your analytics tool already records, without the browser SDK.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.