> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developer.jelliu.co/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developer.jelliu.co/_mcp/server.

# Create an integration

POST https://api.jelliu.co/api/integrations
Content-Type: application/json

Registers an integration with credentials you supply, then immediately runs a connection test.
The response reflects the test:

* `status: active` — the test passed (today, always the case for `webhook`).
* `status: error` with `last_error` — the test threw. Every provider other than `webhook` lands
  here, because their adapters moved to the connected-apps catalogue; `last_error` says so.
* `status: pending_auth` — the test returned "not connected" without an error.

The integration is stored in every case. For OAuth2 providers use
`GET /api/integrations/oauth/authorize` instead, which creates the integration when consent
completes. A `webhook` integration receives pushes at `/webhooks/integrations/v2/{inbound_webhook_uuid}`
with the `x-webhook-secret` header; no endpoint in this API sets `webhook_secret`, and pushes are
answered `401` until one is set.

**Side effects.** Encrypts and stores the credentials; runs the provider test; posts an in-app
notification (connected or failed); writes an audit entry (action `create`, resource
`integration`, credentials redacted).

**Idempotency.** Not idempotent, but a retry cannot create a duplicate: a second create for the
same provider returns `409` while the first integration exists.

**Webhook events.** `audit.log_recorded` for webhooks subscribed to it. See [Webhooks](/webhooks).

**Access**

* **Required scope:** `full`. Humans need the `admin` or `owner` role.
* **Rate limit:** General API (120–600 requests/min per workspace by plan) plus the configuration-mutation limiter — 10 requests/min per workspace, shared with agent and other configuration writes. See [Rate limits](/rate-limits).
* **Plan:** Counts against the integration cap. Every paid plan allows effectively unlimited integrations; a workspace with no active plan is refused.

Reference: https://developer.jelliu.co/api-reference/integrations/post-integrations

## Authentication

- `Authorization` header (bearer token, required) — Workspace API key: `jl_` followed by 64 lowercase hex characters, created by the workspace owner in the dashboard (**Settings → API Keys**) and sent as `Authorization: Bearer jl_...`. The plaintext is shown once, at creation; Jelliu stores only a SHA-256 hash. A workspace can hold up to 25 active keys. | Scope | GET / HEAD | POST / PUT / PATCH / DELETE | Admin-only routes | | --- | --- | --- | --- | | `read` | Yes | No | No | | `write` | Yes | Yes | No | | `full` | Yes | Yes | Yes | Operations restricted to admins or owners reject keys without the `full` scope with `403`, and say so in their description. No key, whatever its scope, can mint or revoke API keys or rotate a webhook secret — that requires a signed-in owner session. A revoked key stops authenticating within about 10 seconds. See [Authentication](/authentication).

## Request

### Body (application/json)

This endpoint expects an object.

- `provider` (enum, required) — Provider to register. Only `webhook` has a built-in adapter; any other value is stored but its connection test fails (see the description). Unknown values are rejected with `400`.
  - Allowed values: `hubspot`, `salesforce`, `zoho`, `pipedrive`, `freshsales`, `airtable`, `google_sheets`, `webhook`, `dynamics365`, `zendesk`, `servicenow`, `kommo`, `monday`, `bitrix24`, `intercom`, `notion`
- `credentials` (map from string to any, required) — Provider-specific credentials as a JSON object (use `{}` for `webhook`). Encrypted at rest and never returned; responses show `***`.
- `label` (string, optional) — Display name shown in the dashboard.
- `fieldMapping` (map from string to string, optional) — Source field name → Jelliu contact field (`phoneNumber`, `email`, `whatsappNumber`, `name`). Applied to inbound `webhook` payloads. For `salesforce`, keys must be valid Salesforce field identifiers or the request is rejected with `400`.
- `campaignId` (string, optional) — Campaign that inbound contacts are added to. Required in practice for `webhook` integrations that receive contacts or leads.

## Response

### 201

Integration stored. Check `status` and `last_error` for the connection test result.

- `data` (object, required) — The full integration record returned by retrieve, create and update: every `Integration` field plus the columns below. `credentials` is still masked; OAuth tokens appear only as encrypted ciphertext.
  - `id` (string, optional) — Unique identifier.
  - `tenant_id` (string, optional) — Workspace that owns the integration.
  - `provider` (enum, optional) — External system. One live integration per provider per workspace. Only `webhook` has a built-in adapter; the CRM values remain for OAuth connections (Kommo powers the agent CRM tools) and for integrations created before CRMs moved to the app catalogue. Integrations created through the OAuth flow can also carry `calcom`.
    - Allowed values: `hubspot`, `salesforce`, `zoho`, `pipedrive`, `freshsales`, `airtable`, `google_sheets`, `webhook`, `dynamics365`, `zendesk`, `servicenow`, `kommo`, `monday`, `bitrix24`, `intercom`, `notion`
  - `label` (string, optional, nullable) — Display name, or `null`.
  - `auth_method` (enum, optional) — How Jelliu authenticates to the provider: `credentials` (JSON supplied at creation, the default), `oauth2` (tokens from the OAuth flow) or `api_key`.
    - Allowed values: `credentials`, `oauth2`, `api_key`
  - `status` (enum, optional) — Connection state: `active`, `paused` (set by you), `error` (the last test or token refresh failed — see `last_error`) or `pending_auth` (not verified yet, or the provider permanently rejected the refresh token and the user must reconnect). Only `active` and `paused` can be set with `PATCH`.
    - Allowed values: `active`, `paused`, `error`, `pending_auth`
  - `credentials` (string, optional) — Always the literal `***`. Stored credentials are never returned.
  - `contact_count` (integer, optional) — Contacts imported through this integration's inbound webhook so far.
  - `last_synced_at` (datetime, optional, nullable) — Last time the inbound webhook received a payload, or `null`.
  - `last_error` (string, optional, nullable) — Reason for the last failure, shown next to `status` `error`; `null` once a test or refresh succeeds. May be provider or product text in Spanish.
  - `field_mapping` (map from string to any, optional) — Source field name → Jelliu contact field (`phoneNumber`, `email`, `whatsappNumber`, `name`). `{}` when unset.
  - `campaign_id` (string, optional, nullable) — Campaign that inbound contacts are added to, or `null`.
  - `oauth_expires_at` (datetime, optional, nullable) — When the current OAuth access token expires; `null` for non-OAuth or non-expiring tokens.
  - `oauth_scopes` (string, optional, nullable) — Scopes granted by the provider, as the single string the provider returned, or `null`.
  - `inbound_webhook_uuid` (string, optional) — Opaque id of the inbound URL `/webhooks/integrations/v2/{inbound_webhook_uuid}`. Every integration has one; only `webhook` integrations accept pushes on it.
  - `created_at` (datetime, optional) — When the integration was created.
  - `updated_at` (datetime, optional) — Last change to the record, including status and sync updates.
  - `deleted_at` (datetime, optional, nullable) — Always `null` in responses; deleted integrations are not returned.
  - `webhook_secret` (string, optional, nullable) — Shared secret a sender must put in the `x-webhook-secret` header when pushing to a `webhook` integration. Returned in plaintext. No endpoint in this API sets it; while it is `null` every push is answered `401`.
  - `external_account_id` (string, optional, nullable) — The provider's own account id (for example a HubSpot portal id), set by the OAuth callback. One provider account can be connected to only one workspace.
  - `sync_cursor` (string, optional, nullable) — Provider pagination cursor from the retired pull sync. Normally `null`.
  - `ticketing_config` (map from string to any, optional, nullable) — Ticketing settings for helpdesk providers, or `null`.
  - `oauth_metadata` (map from string to any, optional, nullable) — Provider data captured by the OAuth callback: `subdomain` (Zendesk, ServiceNow, Kommo), `instance_url` (Salesforce), `api_domain` (Pipedrive), `scopes_verified` (whether the provider reported granted scopes) and `missing_scopes` (requested scopes it did not grant). `null` for non-OAuth integrations.
  - `oauth_access_token` (string, optional, nullable) — Current OAuth access token as encrypted ciphertext (unusable outside Jelliu), or `null`. Wiped on delete.
  - `oauth_refresh_token` (string, optional, nullable) — OAuth refresh token as encrypted ciphertext, or `null`. Wiped on delete.
  - `discovered_schema` (map from string to any, optional, nullable) — Schema inferred from the first payload a `webhook` integration received (same shape as `GET /api/integrations/{integrationId}/webhook-schema`), or `null`.

## Errors

### 400 Bad Request Error

Body failed validation, or a Salesforce field mapping key is not a valid field identifier.

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

### 401 Unauthorized Error

No usable credential. Either the `Authorization` header is missing or is not a well-formed `Bearer jl_…` key, or the key is unknown, revoked or expired. Do not retry with the same key. See [Authentication](/authentication#401-unauthorized).

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

### 403 Forbidden Error

The key is not `full`, the user is not an admin or owner, the workspace is suspended, or the plan gate refused (`BILLING_ERROR`, with `metadata.limit`, `metadata.current` and `metadata.tier`). Every paid plan allows 9999 integrations, so in practice the gate refuses only workspaces with no active plan.

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

### 409 Conflict Error

A live integration for this provider already exists in the workspace. The code is `VALIDATION_FAILED` with HTTP status 409.

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

### 429 Too Many Requests Error

General API limit or the 10/min configuration-mutation limit exceeded.

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

## Examples

### Webhook integration, test passed

**Request**

```json
undefined
```

**Response**

```json
{
  "data": {
    "id": "c71e4a09-2f8b-4d63-8e15-b90d3a6c2f71",
    "tenant_id": "8a41c7e2-3b5d-4f09-a6e1-7d2c9b0f4e58",
    "provider": "webhook",
    "label": "Formulario web",
    "auth_method": "credentials",
    "status": "active",
    "credentials": "***",
    "contact_count": 0,
    "last_synced_at": null,
    "last_error": null,
    "field_mapping": {
      "correo": "email",
      "nombre": "name",
      "telefono": "phoneNumber"
    },
    "campaign_id": "7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d",
    "oauth_expires_at": null,
    "oauth_scopes": null,
    "inbound_webhook_uuid": "e04f7b3a-91c2-4d58-a3e6-1f8b2c9d7a05",
    "created_at": "2026-09-15T13:02:44.000Z",
    "updated_at": "2026-09-15T13:02:44.000Z",
    "deleted_at": null,
    "webhook_secret": null,
    "external_account_id": null,
    "sync_cursor": null,
    "ticketing_config": {},
    "oauth_metadata": {},
    "oauth_access_token": null,
    "oauth_refresh_token": null,
    "discovered_schema": {}
  }
}
```

**SDK Code**

```python Webhook integration, test passed
import requests

url = "https://api.jelliu.co/api/integrations"

headers = {"Authorization": "Bearer <token>"}

response = requests.post(url, headers=headers)

print(response.json())
```

```javascript Webhook integration, test passed
const url = 'https://api.jelliu.co/api/integrations';
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Webhook integration, test passed
package main

import (
	"fmt"
	"net/http"
	"io"
)

func main() {

	url := "https://api.jelliu.co/api/integrations"

	req, _ := http.NewRequest("POST", url, nil)

	req.Header.Add("Authorization", "Bearer <token>")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby Webhook integration, test passed
require 'uri'
require 'net/http'

url = URI("https://api.jelliu.co/api/integrations")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'

response = http.request(request)
puts response.read_body
```

```java Webhook integration, test passed
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.jelliu.co/api/integrations")
  .header("Authorization", "Bearer <token>")
  .asString();
```

```php Webhook integration, test passed
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.jelliu.co/api/integrations', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

echo $response->getBody();
```

```csharp Webhook integration, test passed
using RestSharp;

var client = new RestClient("https://api.jelliu.co/api/integrations");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
IRestResponse response = client.Execute(request);
```

```swift Webhook integration, test passed
import Foundation

let headers = ["Authorization": "Bearer <token>"]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.jelliu.co/api/integrations")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### Non-webhook provider, test failed

**Request**

```json
undefined
```

**Response**

```json
{
  "data": {
    "id": "0f6a2d8e-4b71-4c39-9e05-6d1b8a3c7f22",
    "tenant_id": "8a41c7e2-3b5d-4f09-a6e1-7d2c9b0f4e58",
    "provider": "pipedrive",
    "label": "Pipedrive ventas",
    "auth_method": "credentials",
    "status": "error",
    "credentials": "***",
    "contact_count": 0,
    "last_synced_at": null,
    "last_error": "pipedrive ya no se conecta por aquí: conéctalo desde el catálogo de aplicaciones.",
    "field_mapping": {},
    "campaign_id": null,
    "oauth_expires_at": null,
    "oauth_scopes": null,
    "inbound_webhook_uuid": "9c3d5e7f-1a2b-4c6d-8e0f-2a4b6c8d0e1f",
    "created_at": "2026-09-15T13:05:10.000Z",
    "updated_at": "2026-09-15T13:05:10.000Z",
    "deleted_at": null,
    "webhook_secret": null,
    "external_account_id": null,
    "sync_cursor": null,
    "ticketing_config": {},
    "oauth_metadata": {},
    "oauth_access_token": null,
    "oauth_refresh_token": null,
    "discovered_schema": {}
  }
}
```

**SDK Code**

```python Non-webhook provider, test failed
import requests

url = "https://api.jelliu.co/api/integrations"

headers = {"Authorization": "Bearer <token>"}

response = requests.post(url, headers=headers)

print(response.json())
```

```javascript Non-webhook provider, test failed
const url = 'https://api.jelliu.co/api/integrations';
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Non-webhook provider, test failed
package main

import (
	"fmt"
	"net/http"
	"io"
)

func main() {

	url := "https://api.jelliu.co/api/integrations"

	req, _ := http.NewRequest("POST", url, nil)

	req.Header.Add("Authorization", "Bearer <token>")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby Non-webhook provider, test failed
require 'uri'
require 'net/http'

url = URI("https://api.jelliu.co/api/integrations")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'

response = http.request(request)
puts response.read_body
```

```java Non-webhook provider, test failed
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.jelliu.co/api/integrations")
  .header("Authorization", "Bearer <token>")
  .asString();
```

```php Non-webhook provider, test failed
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.jelliu.co/api/integrations', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

echo $response->getBody();
```

```csharp Non-webhook provider, test failed
using RestSharp;

var client = new RestClient("https://api.jelliu.co/api/integrations");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
IRestResponse response = client.Execute(request);
```

```swift Non-webhook provider, test failed
import Foundation

let headers = ["Authorization": "Bearer <token>"]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.jelliu.co/api/integrations")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### Inbound webhook feeding a campaign

**Request**

```json
{
  "provider": "webhook",
  "label": "Formulario web",
  "fieldMapping": {
    "correo": "email",
    "nombre": "name",
    "telefono": "phoneNumber"
  },
  "campaignId": "7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d"
}
```

**Response**

```json
{
  "data": {
    "id": "c71e4a09-2f8b-4d63-8e15-b90d3a6c2f71",
    "tenant_id": "8a41c7e2-3b5d-4f09-a6e1-7d2c9b0f4e58",
    "provider": "webhook",
    "label": "Formulario web",
    "auth_method": "credentials",
    "status": "active",
    "credentials": "***",
    "contact_count": 0,
    "last_synced_at": null,
    "last_error": null,
    "field_mapping": {
      "correo": "email",
      "nombre": "name",
      "telefono": "phoneNumber"
    },
    "campaign_id": "7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d",
    "oauth_expires_at": null,
    "oauth_scopes": null,
    "inbound_webhook_uuid": "e04f7b3a-91c2-4d58-a3e6-1f8b2c9d7a05",
    "created_at": "2026-09-15T13:02:44.000Z",
    "updated_at": "2026-09-15T13:02:44.000Z",
    "deleted_at": null,
    "webhook_secret": null,
    "external_account_id": null,
    "sync_cursor": null,
    "ticketing_config": {},
    "oauth_metadata": {},
    "oauth_access_token": null,
    "oauth_refresh_token": null,
    "discovered_schema": {}
  }
}
```

**SDK Code**

```python Inbound webhook feeding a campaign
import requests

url = "https://api.jelliu.co/api/integrations"

payload = {
    "provider": "webhook",
    "label": "Formulario web",
    "fieldMapping": {
        "correo": "email",
        "nombre": "name",
        "telefono": "phoneNumber"
    },
    "campaignId": "7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d"
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript Inbound webhook feeding a campaign
const url = 'https://api.jelliu.co/api/integrations';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"provider":"webhook","label":"Formulario web","fieldMapping":{"correo":"email","nombre":"name","telefono":"phoneNumber"},"campaignId":"7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d"}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Inbound webhook feeding a campaign
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.jelliu.co/api/integrations"

	payload := strings.NewReader("{\n  \"provider\": \"webhook\",\n  \"label\": \"Formulario web\",\n  \"fieldMapping\": {\n    \"correo\": \"email\",\n    \"nombre\": \"name\",\n    \"telefono\": \"phoneNumber\"\n  },\n  \"campaignId\": \"7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d\"\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby Inbound webhook feeding a campaign
require 'uri'
require 'net/http'

url = URI("https://api.jelliu.co/api/integrations")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"provider\": \"webhook\",\n  \"label\": \"Formulario web\",\n  \"fieldMapping\": {\n    \"correo\": \"email\",\n    \"nombre\": \"name\",\n    \"telefono\": \"phoneNumber\"\n  },\n  \"campaignId\": \"7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d\"\n}"

response = http.request(request)
puts response.read_body
```

```java Inbound webhook feeding a campaign
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.jelliu.co/api/integrations")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"provider\": \"webhook\",\n  \"label\": \"Formulario web\",\n  \"fieldMapping\": {\n    \"correo\": \"email\",\n    \"nombre\": \"name\",\n    \"telefono\": \"phoneNumber\"\n  },\n  \"campaignId\": \"7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d\"\n}")
  .asString();
```

```php Inbound webhook feeding a campaign
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.jelliu.co/api/integrations', [
  'body' => '{
  "provider": "webhook",
  "label": "Formulario web",
  "fieldMapping": {
    "correo": "email",
    "nombre": "name",
    "telefono": "phoneNumber"
  },
  "campaignId": "7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d"
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp Inbound webhook feeding a campaign
using RestSharp;

var client = new RestClient("https://api.jelliu.co/api/integrations");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"provider\": \"webhook\",\n  \"label\": \"Formulario web\",\n  \"fieldMapping\": {\n    \"correo\": \"email\",\n    \"nombre\": \"name\",\n    \"telefono\": \"phoneNumber\"\n  },\n  \"campaignId\": \"7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift Inbound webhook feeding a campaign
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "provider": "webhook",
  "label": "Formulario web",
  "fieldMapping": [
    "correo": "email",
    "nombre": "name",
    "telefono": "phoneNumber"
  ],
  "campaignId": "7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d"
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.jelliu.co/api/integrations")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### Minimal body

**Request**

```json
{
  "provider": "webhook"
}
```

**Response**

```json
{
  "data": {
    "id": "c71e4a09-2f8b-4d63-8e15-b90d3a6c2f71",
    "tenant_id": "8a41c7e2-3b5d-4f09-a6e1-7d2c9b0f4e58",
    "provider": "webhook",
    "label": "Formulario web",
    "auth_method": "credentials",
    "status": "active",
    "credentials": "***",
    "contact_count": 0,
    "last_synced_at": null,
    "last_error": null,
    "field_mapping": {
      "correo": "email",
      "nombre": "name",
      "telefono": "phoneNumber"
    },
    "campaign_id": "7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d",
    "oauth_expires_at": null,
    "oauth_scopes": null,
    "inbound_webhook_uuid": "e04f7b3a-91c2-4d58-a3e6-1f8b2c9d7a05",
    "created_at": "2026-09-15T13:02:44.000Z",
    "updated_at": "2026-09-15T13:02:44.000Z",
    "deleted_at": null,
    "webhook_secret": null,
    "external_account_id": null,
    "sync_cursor": null,
    "ticketing_config": {},
    "oauth_metadata": {},
    "oauth_access_token": null,
    "oauth_refresh_token": null,
    "discovered_schema": {}
  }
}
```

**SDK Code**

```python Minimal body
import requests

url = "https://api.jelliu.co/api/integrations"

payload = { "provider": "webhook" }
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript Minimal body
const url = 'https://api.jelliu.co/api/integrations';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"provider":"webhook"}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Minimal body
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.jelliu.co/api/integrations"

	payload := strings.NewReader("{\n  \"provider\": \"webhook\"\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby Minimal body
require 'uri'
require 'net/http'

url = URI("https://api.jelliu.co/api/integrations")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"provider\": \"webhook\"\n}"

response = http.request(request)
puts response.read_body
```

```java Minimal body
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.jelliu.co/api/integrations")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"provider\": \"webhook\"\n}")
  .asString();
```

```php Minimal body
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.jelliu.co/api/integrations', [
  'body' => '{
  "provider": "webhook"
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp Minimal body
using RestSharp;

var client = new RestClient("https://api.jelliu.co/api/integrations");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"provider\": \"webhook\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift Minimal body
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = ["provider": "webhook"] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.jelliu.co/api/integrations")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```