# Send from your server

> Trigger transactional pushes, such as order updates, from your backend with your project key.

Source: https://docs.grovs.io/docs/engagement/push-campaigns/send-from-your-server

Send pushes from your backend when something happens in your system: an order shipped, a payment failed, a class is about to start. Each call creates a push campaign with the same delivery, report and tap tracking as a dashboard campaign. It shows in **Engage › Push** with the source `api`.

Keep the project key on your server. Never put it in an app or a web page.

## Authenticate

Send two headers with every request:

| Header        | Value                                                             |
| ------------- | ----------------------------------------------------------------- |
| `PROJECT-KEY` | Your project key, from **Developers › API Key** in the dashboard. |
| `ENVIRONMENT` | `production` or `test`. The environment the push is sent from.    |

These are the same headers as [link generation](https://docs.grovs.io/docs/api-reference/links#generate-a-link). See [Authentication](https://docs.grovs.io/docs/api-reference/authentication).

## Send a push

Medly, a fictional pharmacy app, tells a user their prescription is ready:

```bash
curl -X POST https://sdk.sqd.link/api/v1/sdk/server/push \
  -H "PROJECT-KEY: $GROVS_PROJECT_KEY" \
  -H "ENVIRONMENT: production" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Your prescription is ready",
    "body": "Pick it up at Medly Central today",
    "link_id": 4821,
    "data": { "order_id": "ord_8812" },
    "audience": { "type": "identifiers", "recipients": ["user_1042"] }
  }'
```

On self-hosted Grovs, use your own SDK host, such as `https://sdk.grovs.example.com`.

The response is `202 Accepted` with the campaign in status `evaluating`. Grovs resolves the audience and hands the pushes to Apple and Google within a couple of seconds.

### What you can send

The body takes the same message and schedule fields as a dashboard campaign:

| Field                                    | Notes                                                                                                                                                         |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title`, `body`                          | Required. Up to 255 and 1,000 characters.                                                                                                                     |
| `name`                                   | Optional label in the dashboard. Defaults to the title.                                                                                                       |
| `image_url`                              | Optional. An `https://` URL of a PNG, JPEG or GIF. Uploads aren't available here.                                                                             |
| `sound`                                  | Optional. `default` or a sound file bundled in your app.                                                                                                      |
| `link_id`                                | Optional. The numeric ID of a Grovs link in this project.                                                                                                     |
| `data`                                   | Optional. An object of custom keys, up to 2 KB.                                                                                                               |
| `audience`                               | Required. `identifiers` (your user IDs), `visitors` (visitor IDs, the UUIDs Grovs assigns) or `segment` with a `segment_id`. Lists hold up to 10,000 entries. |
| `scheduled_at`, `local_time`, `timezone` | Optional. Schedule the push instead of sending now, as in [Delivery](https://docs.grovs.io/docs/engagement/push-campaigns/create-a-campaign#4-delivery).      |

Experiments (variants, control groups and goals) are dashboard-only.

For the full request and response, see the [Server push API reference](https://docs.grovs.io/docs/api-reference/server-push).

## Check the result

Save the campaign `id` from the response and read the live report:

```bash
curl https://sdk.sqd.link/api/v1/sdk/server/push/k9Qx2 \
  -H "PROJECT-KEY: $GROVS_PROJECT_KEY" \
  -H "ENVIRONMENT: production"
```

The `report` object holds the same numbers as the dashboard report. See [Delivery and reporting](https://docs.grovs.io/docs/engagement/push-campaigns/delivery-and-reporting).

## How server pushes differ

- **Not held back.** Server pushes are transactional. The project control group in **Engagement Settings** doesn't apply to them.
- **Still capped.** The daily push cap, if set, counts them like any other push.
- **Rate limited.** Per project per minute: up to 600 campaigns, 10 of them with a segment audience, and 100,000 listed recipients. Over a limit, the request fails with `429` and `error_code: "rate_limited"`. A refused request creates nothing and uses no budget, so retry after a short wait.

## Errors

Every error has an `error` message. Plan and rate-limit errors also carry an `error_code`.

| Status | Meaning                                                                                                                                        |
| ------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Missing `PROJECT-KEY`, or `ENVIRONMENT` isn't `production` or `test`.                                                                          |
| `403`  | `Invalid credentials`: the project key is wrong. Or, with `error_code: "enterprise_required"`, your Cloud plan doesn't include the server API. |
| `404`  | Push campaigns aren't available on this installation, or no campaign has that ID.                                                              |
| `422`  | A field is invalid. The message names it, for example `Link not found`, `Segment not found` or `data must be an object`.                       |
| `429`  | `rate_limited`: over one of the per-minute limits above.                                                                                       |
