# Getting started

> From access to your first webhook in five minutes: request a token, make your first call, create a destination and trigger a test alert in the sandbox.

Page: https://safeonuba.com/en/developers/getting-started · SafeOnuba API v1 (`api_version: "2026-09-24"`), in preview: the contract may change.

This guide goes from access to your first received alert, all in the sandbox. You need a terminal with `curl` and, for step 5, a public https URL where you can receive webhooks.

The examples read the credentials from environment variables. Don’t write them in the code.

```bash
export SAFEONUBA_CLIENT_ID="example-client"
export SAFEONUBA_CLIENT_SECRET="cs_…"
```

## 1. Request access

We register access ourselves, customer by customer, and the reason is recorded. The plant sees in its dashboard (Settings → Integrations) who has access, to what and why.

When you are registered you receive a `client_id` and a `client_secret`, which starts with `cs_`. **The secret is shown only once**: store it in your secrets manager at that moment.

To request it, write to us at [info@safeonuba.com](mailto:info@safeonuba.com?subject=SafeOnuba%20API%20access) with your company, the plant and what you want to integrate.

## 2. Request a token

The token is requested with OAuth2 `client_credentials` and lasts one hour.

```bash
curl -X POST https://sandbox.api.safeonuba.com/v1/oauth/token \
  -d grant_type=client_credentials \
  -d client_id="$SAFEONUBA_CLIENT_ID" \
  -d client_secret="$SAFEONUBA_CLIENT_SECRET"
```

```javascript
const res = await fetch('https://sandbox.api.safeonuba.com/v1/oauth/token', {
  method: 'POST',
  body: new URLSearchParams({
    grant_type: 'client_credentials',
    client_id: process.env.SAFEONUBA_CLIENT_ID,
    client_secret: process.env.SAFEONUBA_CLIENT_SECRET,
  }),
});
const { access_token } = await res.json();
```

```python
import os
import requests

res = requests.post(
    "https://sandbox.api.safeonuba.com/v1/oauth/token",
    data={
        "grant_type": "client_credentials",
        "client_id": os.environ["SAFEONUBA_CLIENT_ID"],
        "client_secret": os.environ["SAFEONUBA_CLIENT_SECRET"],
    },
    timeout=10,
)
res.raise_for_status()
access_token = res.json()["access_token"]
```

```csharp
using System.Net.Http.Json;
using System.Text.Json;

using var http = new HttpClient();
var res = await http.PostAsync("https://sandbox.api.safeonuba.com/v1/oauth/token",
    new FormUrlEncodedContent(new Dictionary<string, string>
    {
        ["grant_type"] = "client_credentials",
        ["client_id"] = Environment.GetEnvironmentVariable("SAFEONUBA_CLIENT_ID")!,
        ["client_secret"] = Environment.GetEnvironmentVariable("SAFEONUBA_CLIENT_SECRET")!,
    }));
res.EnsureSuccessStatusCode();
var token = (await res.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("access_token").GetString();
```

The response:

```json
{
  "access_token": "at_9c1e…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "alerts:read alerts:write musters:read"
}
```

Keep the `access_token` for the next steps:

```bash
export SAFEONUBA_TOKEN="…"
```

## 3. Make your first call

`GET /v1/sites` returns the sites you have access to. Any scope will do, so it is the best way to check that the token works.

```bash
curl https://sandbox.api.safeonuba.com/v1/sites \
  -H "Authorization: Bearer $SAFEONUBA_TOKEN"
```

Note the `id` of your sandbox site: you will use it when you run a scenario.

## 4. Create a webhook destination

A destination is the URL of your receiver and the events you want it to get. It needs the `webhooks:manage` scope.

```bash
curl -X POST https://sandbox.api.safeonuba.com/v1/webhooks \
  -H "Authorization: Bearer $SAFEONUBA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://controlroom.customer.example/safeonuba",
    "event_types": ["alert.*", "muster.*"]
  }'
```

The response includes the signing `secret`, which starts with `whsec_`.

> **The webhook secret is only shown now**
>
> Store it in the same place as the `client_secret`. If you lose it, rotate it with `POST /v1/webhooks/{id}/rotate-secret`.

## 5. Receive and verify

Your receiver has to do three things with each delivery:

1. Check the signature in `X-SafeOnuba-Signature` over the **raw** body.
2. Respond `2xx` in under 5 seconds.
3. Process the event afterwards, outside that request.

[Verifying a webhook signature](https://safeonuba.com/en/developers/webhooks/verify-signature) has the complete receiver in Node, Python and C#. Once you have it deployed, check that something reaches it:

```bash
curl -X POST https://sandbox.api.safeonuba.com/v1/webhooks/DESTINATION_ID/test \
  -H "Authorization: Bearer $SAFEONUBA_TOKEN"
```

An `alert.created` arrives, marked `simulated: true` and signed like a real one.

## 6. Trigger a test SOS

In the sandbox, a scenario makes a simulated watch press the SOS. What you receive is the same as you would receive from a real watch.

```bash
curl -X POST https://sandbox.api.safeonuba.com/v1/sandbox/scenarios \
  -H "Authorization: Bearer $SAFEONUBA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "scenario": "sos" }'
```

Your receiver gets an `alert.created` with the complete alert in `data.alert`. If nobody closes it, it closes itself after 10 minutes.

## 7. Acknowledge it from your control room

With the `alerts:write` scope, your control room can take charge of the alert. Always on behalf of a person: the `actor` is the operator who acknowledged it, and is recorded in the alert’s timeline.

```bash
curl -X PUT https://sandbox.api.safeonuba.com/v1/alerts/ALERT_ID \
  -H "Authorization: Bearer $SAFEONUBA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "acknowledged",
    "actor": { "name": "E. Example", "external_id": "OP-117" }
  }'
```

Your receiver gets `alert.acknowledged`, and in the SafeOnuba dashboard the alert shows as acknowledged “via integration”.

## Next steps

- [Authentication and scopes](https://safeonuba.com/en/developers/authentication): caching the token and rotating the secret without downtime.
- [Webhooks](https://safeonuba.com/en/developers/webhooks): retries, duplicates, order and filters.
- [Sandbox](https://safeonuba.com/en/developers/sandbox): the full evacuation scenario, from start to finish.
