# Local Development

## Starting the dev server

```bash
sinch functions dev
```

**Node.js** functions restart automatically when you save changes. **C#** functions use `dotnet watch run`.

Default port is `3000`. Override with:

```bash
sinch functions dev --port 8080
```

If the port is already in use, `dev` fails immediately with an error instead of starting silently — free the port or choose another with `--port`.

## Tunnel

The tunnel proxies requests from the Sinch platform to your local machine. Without it, Sinch callbacks cannot reach `localhost`.

On first run, the CLI asks for your tunnel preference. Your answer is saved in `sinch.json` under `tunnel.preference`.

```bash
sinch functions dev --tunnel      # Force tunnel on
sinch functions dev --no-tunnel   # Force tunnel off
```

When active, the tunnel registers a public endpoint and the platform automatically configures your Voice application's callback URL to point at it. `dev` warns you when this happens: real calls to that Voice application route to your machine until you stop dev — so use a dedicated development Voice app, not one handling production traffic.

## Debugging

### Node.js

```bash
sinch functions dev --debug
```

Starts the Node.js inspector on port `9229`. The CLI creates `.vscode/launch.json` automatically if it does not exist.

- **VS Code / Cursor** — Press F5 to attach
- **WebStorm** — Run > Attach to Node.js/Chrome > `localhost:9229`
- **Manual** — connect to `ws://localhost:9229`


### C#

```bash
sinch functions dev --debug
```

Creates `.vscode/launch.json` and `.vscode/tasks.json`. For Visual Studio: Debug > Attach to Process > `dotnet.exe`. For Rider: Run > Attach to Process.

### Debug environment variables

| Variable | Effect |
|  --- | --- |
| `SINCH_DEBUG=1` | Verbose CLI debug logging (operations, timing) |
| `SINCH_DEBUG=2` | + HTTP requests (method, URL, status) |
| `SINCH_DEBUG=3` | + HTTP headers and response bodies (redacted) |


## Testing endpoints

Voice API v2 functions receive every `call.*` event at `voiceWebhook`, matched by the request body rather than by path:

```bash
# call.incoming — inbound call reaches the function root
curl -s -X POST http://localhost:3000/ \
  -H "Content-Type: application/json" \
  -d '{
    "event": "call.incoming",
    "call": {
      "callId": "01LOCALTEST0000000000000001",
      "sessionId": "01LOCALTEST0000000000000002",
      "from": { "type": "PHONE", "phone": { "number": "+15551234567" } },
      "to": { "type": "PHONE", "phone": { "number": "+15559876543" } },
      "direction": "INBOUND"
    }
  }'

# call.menu — collected digits handed back to the function (simulating DTMF press "1")
curl -s -X POST http://localhost:3000/ \
  -H "Content-Type: application/json" \
  -d '{
    "event": "call.menu",
    "call": { "callId": "01LOCALTEST0000000000000001" },
    "menu": { "menuName": "main", "input": "1" }
  }'
```

For a Voice API v1 (legacy) function, the same idea applies at the callback's own path — `POST /ice` with an `IceCallbackModel`-shaped body, for example.

## Secrets

Secrets are sensitive values stored in your OS keychain and injected at runtime.

### Pattern

1. Declare the key in `.env` (Node.js) or `appsettings.json` (C#) with an empty value
2. Store the actual value: `sinch secrets add OPENAI_API_KEY sk-proj-PLACEHOLDER`
3. The runtime loads values from the keychain at startup


### Use in code

```typescript
const apiKey = process.env.OPENAI_API_KEY;
```

### Manage secrets

```bash
sinch secrets list                         # List key names (values hidden)
sinch secrets get OPENAI_API_KEY           # Show metadata, --show for value
sinch secrets delete OPENAI_API_KEY        # Remove from keychain
```

### C# secrets

`sinch functions init` runs `dotnet user-secrets init` automatically. To add manually:

```bash
dotnet user-secrets set OPENAI_API_KEY sk-proj-PLACEHOLDER
```

### On deploy

The CLI reads your `.env` or `appsettings.json`. For keys with empty values, it looks up the actual value from the keychain. Secret variables are encrypted at rest and never appear in logs.

## Credential injection

The dev server injects the Voice API v1 application credentials as environment variables, but only for a function wired to a v1 voice application — one whose `.env` or `appsettings.json` carries a `VOICE_APPLICATION_KEY`. A Voice v2 function authenticates with the project key pair instead, so nothing is injected for it.

| Variable | Source |
|  --- | --- |
| `SINCH_APPLICATION_KEY` | OS keychain (Voice application key) |
| `SINCH_APPLICATION_SECRET` | OS keychain (Voice application secret) |


## What gets excluded from deployment

`node_modules/`, `.env`, `.env.local`, `bin/`, `obj/`, `.git/`, `.vscode/`, `.idea/`, `.vs/`, `.cursor/`, `*.zip`, `*.user`, `*.suo`