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

# Update notification preferences

> Persist graph-wide profile-diff subscriptions and email, Slack, and webhook settings.

Updates the authenticated organization's notification preferences and, when supplied, its default webhook URL or secret. The update is a partial upsert: omitted fields keep their current values. Email and Slack delivery are graph-wide for enabled fields; this endpoint does not change which people are visible from [List events](/api-reference/events/list).

The Gateway stores the webhook URL and secret in organization settings, but it does not POST profile diffs to that URL. Poll the events endpoint instead.

## Request body

All fields are optional. Send JSON with `Content-Type: application/json`.

<ParamField body="fields" type="string[]">
  Replaces the subscribed field list when supplied. Supported values are `open_to_work`, `between_roles`, `exploring`, `left_company`, `joined_company`, `removed_position`, `headline`, `about`, and `role_description`. Unknown and non-string array items are ignored, and duplicates are removed. At least one known value must remain, or the Gateway returns `400`. Omit this field to preserve the current list. A non-array value currently selects the default field list rather than failing validation; always send an array to avoid an unintended reset.
</ParamField>

<ParamField body="email_enabled" type="boolean">
  Enables or disables email delivery. Omit to preserve the current value.
</ParamField>

<ParamField body="emails" type="string[]">
  Replaces the saved email list. String entries containing `@` are trimmed, lowercased, and the first 20 accepted entries are stored. Omit to preserve the current list. The implementation does not apply a stricter email-format validator.
</ParamField>

<ParamField body="slack_enabled" type="boolean">
  Enables or disables Slack delivery. Omit to preserve the current value. A successful send still requires a Slack installation and a selected channel.
</ParamField>

<ParamField body="slack_channel_id" type="string">
  Optional channel identifier, trimmed to at most 64 characters. Omit to preserve the current value.
</ParamField>

<ParamField body="slack_channel_name" type="string">
  Optional display name, trimmed to at most 80 characters. Omit to preserve the current value.
</ParamField>

<ParamField body="webhook_url" type="string">
  Optional organization default webhook URL. It is trimmed when stored; a non-empty value updates it. An empty string is treated as no update by the Gateway. The Gateway stores it and reports only whether one is configured. It does not validate or deliver to the URL here.
</ParamField>

<ParamField body="webhook_secret" type="string">
  Optional organization webhook secret. It is trimmed when stored and is never returned. An empty string does not replace an existing secret.
</ParamField>

## Response

```json 200 theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "data": {
    "org_id": "a7590925-3e96-4f89-898c-bf7ed02fbd97",
    "fields": ["open_to_work", "left_company"],
    "email_enabled": true,
    "emails": ["you@acme.com"],
    "slack_enabled": false,
    "slack_channel_id": null,
    "slack_channel_name": null,
    "webhook": { "configured": true }
  }
}
```

`org_id` is a string; `fields`, `emails`, and the enabled flags are non-nullable. `slack_channel_id` and `slack_channel_name` are nullable strings. `webhook.configured` is a non-nullable boolean based only on whether a default webhook URL exists. Neither the URL nor the secret is returned.

## Errors, side effects, and retries

| Status | Meaning                                                                             | Recovery                                                     |
| ------ | ----------------------------------------------------------------------------------- | ------------------------------------------------------------ |
| `400`  | Invalid platform-org scoping header, or `fields` was supplied without a known value | Correct the header or body; do not retry unchanged.          |
| `401`  | Missing, malformed, or revoked API key                                              | Supply a valid key.                                          |
| `429`  | Shared Gateway limit exceeded                                                       | Honor `Retry-After` and back off.                            |
| `5xx`  | Gateway or database failure                                                         | Re-read preferences and reconcile before retrying the write. |

A successful call persists notification preferences and any supplied webhook setting. There is no idempotency key. Repeating the same complete body should converge to the same settings, but after a timeout do not assume whether the write committed: `GET /signals/preferences` first to reconcile readable preferences. `GET /me` can confirm only whether a webhook is configured, not the URL or secret; do not treat that boolean as proof a particular webhook update succeeded. Preferences and webhook settings are written separately, so a failure can leave a partial update. The default Gateway limit is 300 requests per minute per client IP, not per API key.

<RequestExample>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl --fail-with-body --max-time 30 -X PUT "https://api.freshtalent.ai/v1/signals/preferences" \
    -H "Authorization: Bearer $FRESHTALENT_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "fields": ["open_to_work", "left_company"],
      "email_enabled": true,
      "emails": ["you@acme.com"],
      "slack_enabled": false,
      "webhook_url": "https://example.com/hooks/ft"
    }'
  ```

  ```js JavaScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  const response = await fetch("https://api.freshtalent.ai/v1/signals/preferences", {
    method: "PUT",
    signal: AbortSignal.timeout(30_000),
    headers: {
      Authorization: `Bearer ${process.env.FRESHTALENT_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      fields: ["open_to_work", "left_company"],
      email_enabled: true,
      emails: ["you@acme.com"],
      slack_enabled: false,
    }),
  });
  if (!response.ok) throw new Error(`FreshTalent HTTP ${response.status}`);
  console.log((await response.json()).data);
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  import json
  import os
  from urllib.request import Request, urlopen

  body = json.dumps({
      "fields": ["open_to_work", "left_company"],
      "email_enabled": True,
      "emails": ["you@acme.com"],
      "slack_enabled": False,
  }).encode()
  request = Request(
      "https://api.freshtalent.ai/v1/signals/preferences",
      data=body,
      method="PUT",
      headers={
          "Authorization": "Bearer " + os.environ["FRESHTALENT_API_KEY"],
          "Content-Type": "application/json",
      },
  )
  with urlopen(request, timeout=30) as response:
      print(json.load(response)["data"])
  ```
</RequestExample>

## Related

* [Get notification preferences](/api-reference/signals/get-preferences)
* [Send test digest](/api-reference/signals/test)
* [Get account](/api-reference/account/me)
* [List events](/api-reference/events/list)
