Developers
Set your API contextPick a region and space ID once. Every sample fills in your base URL.
Where do I find these?

In Coevera: open your space's API application and choose Show Access. It shows the space ID and the base URL, whose host starts with your region.

From the API: if you know the space ID, the discovery endpoint GET /api/discovery/spaces/{space_id} returns its region and base URLs, without credentials.

Only the region and space ID are kept, in this browser. Never paste a key or password here.

More in the quickstart

WebhooksWebhooks overview

Webhooks overview

Get a signed POST when records change instead of polling. Register a webhook through the API, choose from 138 events, and verify every delivery.

Answer

Create a webhook with POST /entities/Webhooks: url, events and insecure_ssl are required, and a signature UUID makes Coevera sign every delivery. Deliveries are POSTs of a JSON array of up to 100 events, signed in WebHook-Signature with HMAC-SHA256 of the raw body. Answer any 2xx within 10 seconds; failed deliveries are retried for about 24 hours.

A webhook tells Coevera to call your URL when records change, so you do not have to poll the API. You register it through the API; the CRM has no screen for webhooks.

How webhooks work

  1. A record changes in your space.
  2. Coevera POSTs a JSON array of events to your URL, signed with your webhook's secret.
  3. Your receiver checks the signature against the raw body.
  4. Your receiver answers 2xx within 10 seconds, then processes the events.

Before you begin

  • An API application whose permissions allow Write on Webhooks (under Misc; see permissions and scopes), and its credentials in COEVERA_API_KEY and COEVERA_API_SECRET (see authentication). Read is enough to list webhooks.
  • A receiver reachable from the internet over HTTPS. To build one, follow the webhook receiver recipe; to try it on your machine first, see testing locally.
  • Your region and space ID in the bar at the top of the page; the samples fill them in.

The samples below share this setup: the base URL, the credentials and your receiver's URL.

COEVERA_BASE="https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}"
# Your receiver: Coevera POSTs every delivery to this URL.
RECEIVER_URL="https://hooks.example.com/coevera"
const base = `https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}`;
const auth = Buffer.from(`${env.COEVERA_API_KEY}:${env.COEVERA_API_SECRET}`).toString("base64");
// Your receiver: Coevera POSTs every delivery to this URL.
const receiverUrl = "https://hooks.example.com/coevera";

async function coevera(method: string, path: string, body?: unknown) {
  const res = await fetch(`${base}${path}`, {
    method,
    headers: { Authorization: `Basic ${auth}`, "Content-Type": "application/json" },
    body: body === undefined ? undefined : JSON.stringify(body),
  });
  const json = await res.json();
  if (!res.ok) throw new Error(`HTTP ${res.status} ${json.name}: ${json.message}`);
  return json;
}
BASE = "https://{region}.api.coevera.com/api/v100/rest/spaces/{space_id}"
session = requests.Session()
session.auth = (os.environ["COEVERA_API_KEY"], os.environ["COEVERA_API_SECRET"])
# Your receiver: Coevera POSTs every delivery to this URL.
RECEIVER_URL = "https://hooks.example.com/coevera"


def coevera(method, path, **kwargs):
    response = session.request(method, f"{BASE}{path}", timeout=30, **kwargs)
    body = response.json()
    if not response.ok:
        sys.exit(f"HTTP {response.status_code} {body.get('name')}: {body.get('message')}")
    return body
COEVERA_GRAPHQL="https://{region}.api.coevera.com/api/v100/app/space/{space_id}/graphql/public"
# Your receiver: Coevera POSTs every delivery to this URL.
RECEIVER_URL="https://hooks.example.com/coevera"

# gql: POST the GraphQL request read from stdin. GraphQL reports errors in the body, often
# with HTTP 200, so fail on those too.
gql() {
  local response
  response=$(curl -sS --fail-with-body "$COEVERA_GRAPHQL" \
    -u "$COEVERA_API_KEY:$COEVERA_API_SECRET" \
    -H "Content-Type: application/json" \
    -d @-)
  if jq -e '.errors' <<<"$response" >/dev/null; then
    echo "$response" >&2
    return 1
  fi
  echo "$response"
}
const endpoint = `https://{region}.api.coevera.com/api/v100/app/space/{space_id}/graphql/public`;
const auth = Buffer.from(`${env.COEVERA_API_KEY}:${env.COEVERA_API_SECRET}`).toString("base64");
// Your receiver: Coevera POSTs every delivery to this URL.
const receiverUrl = "https://hooks.example.com/coevera";

// gql: one GraphQL request. GraphQL reports errors in the body, often with HTTP 200: check both.
async function gql(query: string, variables: object = {}) {
  const res = await fetch(endpoint, {
    method: "POST",
    headers: { Authorization: `Basic ${auth}`, "Content-Type": "application/json" },
    body: JSON.stringify({ query, variables }),
  });
  const body = await res.json();
  if (!res.ok || body.errors) throw new Error(`HTTP ${res.status}: ${JSON.stringify(body.errors ?? body)}`);
  return body.data;
}
ENDPOINT = "https://{region}.api.coevera.com/api/v100/app/space/{space_id}/graphql/public"
session = requests.Session()
session.auth = (os.environ["COEVERA_API_KEY"], os.environ["COEVERA_API_SECRET"])
# Your receiver: Coevera POSTs every delivery to this URL.
RECEIVER_URL = "https://hooks.example.com/coevera"


def gql(query, variables=None):
    """One GraphQL request. GraphQL reports errors in the body, often with HTTP 200."""
    response = session.post(ENDPOINT, json={"query": query, "variables": variables or {}}, timeout=30)
    body = response.json()
    if not response.ok or body.get("errors"):
        sys.exit(f"HTTP {response.status_code}: {json.dumps(body.get('errors', body))}")
    return body["data"]

Create a webhook

Send the URL, the events and insecure_ssl, which are required, and a signature: a UUID you generate and keep as your receiver's secret. In GraphQL, events are written without the dot (OpportunityWon) and options is a JSON string. Every field and operation of the entity is on the Webhooks reference page.

# The signing secret: a random UUID. Keep it as WEBHOOK_SECRET in your receiver.
SECRET=$(od -An -tx1 -N16 /dev/urandom | tr -d ' \n' |
  sed -E 's/^(.{8})(.{4}).(.{3}).(.{3})(.{12})$/\1-\2-4\3-a\4-\5/')
webhook=$(curl -sS --fail-with-body "$COEVERA_BASE/entities/Webhooks" \
  -u "$COEVERA_API_KEY:$COEVERA_API_SECRET" \
  -H "Content-Type: application/json" \
  -d @- <<JSON
{
  "url": "$RECEIVER_URL",
  "events": ["Opportunity.Won", "Opportunity.Lost"],
  "signature": "$SECRET",
  "insecure_ssl": false,
  "options": {
    "entity": {
      "Opportunity": { "load-only": ["id", "name", "owner_id", "step_id", "value", "revision"] }
    },
    "chunk_size": 50
  }
}
JSON
)
echo "$webhook"
WEBHOOK_ID=$(jq -er '.data.id' <<<"$webhook")
echo "WEBHOOK_SECRET=$SECRET"
// The signing secret: a random UUID. Keep it as WEBHOOK_SECRET in your receiver.
const secret = randomUUID();
const created = await coevera("POST", "/entities/Webhooks", {
  url: receiverUrl,
  events: ["Opportunity.Won", "Opportunity.Lost"],
  signature: secret,
  insecure_ssl: false,
  options: {
    entity: { Opportunity: { "load-only": ["id", "name", "owner_id", "step_id", "value", "revision"] } },
    chunk_size: 50,
  },
});
const webhookId: string = created.data.id;
console.log("Webhook:", webhookId, "WEBHOOK_SECRET:", secret);
# The signing secret: a random UUID. Keep it as WEBHOOK_SECRET in your receiver.
secret = str(uuid.uuid4())
created = coevera(
    "POST",
    "/entities/Webhooks",
    json={
        "url": RECEIVER_URL,
        "events": ["Opportunity.Won", "Opportunity.Lost"],
        "signature": secret,
        "insecure_ssl": False,
        "options": {
            "entity": {"Opportunity": {"load-only": ["id", "name", "owner_id", "step_id", "value", "revision"]}},
            "chunk_size": 50,
        },
    },
)
webhook_id = created["data"]["id"]
print("Webhook:", webhook_id, "WEBHOOK_SECRET:", secret)
# The signing secret: a random UUID. Keep it as WEBHOOK_SECRET in your receiver.
SECRET=$(od -An -tx1 -N16 /dev/urandom | tr -d ' \n' |
  sed -E 's/^(.{8})(.{4}).(.{3}).(.{3})(.{12})$/\1-\2-4\3-a\4-\5/')
webhook=$(jq -n --arg url "$RECEIVER_URL" --arg secret "$SECRET" '{
  query: "mutation CreateWebhook($input: CreateWebhookInput!) { createWebhook(input: $input) { webhook { id url events insecureSsl options } } }",
  variables: {
    input: {
      url: $url,
      events: ["OpportunityWon", "OpportunityLost"],
      signature: $secret,
      insecureSsl: false,
      options: ({
        entity: { Opportunity: { "load-only": ["id", "name", "owner_id", "step_id", "value", "revision"] } },
        chunk_size: 50
      } | tojson)
    }
  }
}' | gql)
echo "$webhook"
WEBHOOK_ID=$(jq -er '.data.createWebhook.webhook.id' <<<"$webhook")
echo "WEBHOOK_SECRET=$SECRET"
// The signing secret: a random UUID. Keep it as WEBHOOK_SECRET in your receiver.
const secret = randomUUID();
const created = await gql(
  `mutation CreateWebhook($input: CreateWebhookInput!) {
    createWebhook(input: $input) { webhook { id url events insecureSsl options } }
  }`,
  {
    input: {
      url: receiverUrl,
      events: ["OpportunityWon", "OpportunityLost"],
      signature: secret,
      insecureSsl: false,
      options: JSON.stringify({
        entity: { Opportunity: { "load-only": ["id", "name", "owner_id", "step_id", "value", "revision"] } },
        chunk_size: 50,
      }),
    },
  },
);
const webhookId: string = created.createWebhook.webhook.id;
console.log("Webhook:", webhookId, "WEBHOOK_SECRET:", secret);
# The signing secret: a random UUID. Keep it as WEBHOOK_SECRET in your receiver.
secret = str(uuid.uuid4())
created = gql(
    """
    mutation CreateWebhook($input: CreateWebhookInput!) {
      createWebhook(input: $input) { webhook { id url events insecureSsl options } }
    }
    """,
    {
        "input": {
            "url": RECEIVER_URL,
            "events": ["OpportunityWon", "OpportunityLost"],
            "signature": secret,
            "insecureSsl": False,
            "options": json.dumps({
                "entity": {"Opportunity": {"load-only": ["id", "name", "owner_id", "step_id", "value", "revision"]}},
                "chunk_size": 50,
            }),
        }
    },
)
webhook_id = created["createWebhook"]["webhook"]["id"]
print("Webhook:", webhook_id, "WEBHOOK_SECRET:", secret)
201 CreatedRecorded response · 2026-10-15 · docs sandbox, de-identified
{
  "success": true,
  "data": {
    "is_delete_protected": false,
    "id": "1e000000-0000-4000-a000-000000000001",
    "is_deleted": false,
    "modified": "2026-10-15 09:00:00.000000+00:00",
    "created": "2026-10-15 09:00:00.000035+00:00",
    "application": "https://eu-central.api.coevera.com/api/v100/rest/spaces/nv2_northwind_docs/entities/Applications/00000000-0000-4000-a000-000000000001",
    "client": null,
    "application_id": "00000000-0000-4000-a000-000000000001",
    "client_id": null,
    "insecure_ssl": false,
    "options": {
      "entity": {
        "Opportunity": {
          "load-only": [
            "id",
            "name",
            "owner_id",
            "step_id",
            "value",
            "revision"
          ]
        }
      },
      "chunk_size": 50
    },
    "signature": "1e000000-0000-4000-a000-000000000002",
    "url": "https://hooks.example.com/coevera",
    "events": [
      "Opportunity.Won",
      "Opportunity.Lost"
    ]
  }
}
Show all 36 linesShow less
200 OKRecorded response · 2026-10-15 · docs sandbox, de-identified
{
  "data": {
    "createWebhook": {
      "webhook": {
        "id": "1e000000-0000-4000-a000-000000000001",
        "url": "https://hooks.example.com/coevera",
        "events": [
          "OpportunityLost",
          "OpportunityWon"
        ],
        "insecureSsl": false,
        "options": "{\"entity\": {\"Opportunity\": {\"load-only\": [\"id\", \"name\", \"owner_id\", \"step_id\", \"value\", \"revision\"]}}, \"chunk_size\": 50}"
      }
    }
  }
}

What the API checks when you create or update a webhook:

Field Rule
url Required. Must start with https:// or http://. An address on a private network, localhost included, is refused (400).
events Required, at least one, from the events catalogue (422 otherwise).
insecure_ssl Required. true skips the check of your receiver's TLS certificate; keep it false.
signature Optional, but without it deliveries carry no authentication. Must be a UUID (422 otherwise); the API stores and returns it in lowercase.
options Optional, defaults to {}; null is refused. See filtering and options.

A refused request answers with the error body that errors describes: check code and message, not only the status.

Warning

Every read of a webhook returns its signature, the secret your receiver verifies deliveries with. Keep webhook responses out of logs, and give the Webhooks permission only to the applications that manage them.

A webhook registered with an API application's credentials has no client_id. Its deliveries include every record the change touched, private records too.

The payload

Each delivery is a POST to your URL with Content-Type: application/json and a body that is a JSON array: one chunk of up to chunk_size events, 100 by default. Each event carries (from the code):

Field What it holds
webhook The webhook's registration, such as its id, url, events and options, without its signature
event The event that fired, such as Opportunity.Won
event_time When the change was saved: UTC, ISO 8601 without an offset
team_space_id Your space ID
entity The record as the REST API returns it, shaped by load-only and expand in the options (querying)
related_entity The document for a DocumentLinked event, otherwise null

The record is read when the delivery is built, not when it was written, and a retry sends the same body again. The delivery has no event or delivery ID of its own: deduplicate on event, entity.id, entity.revision and event_time, as the security page explains.

Verify the signature

When the webhook has a signature, every delivery carries these headers:

Header Value
WebHook-Signature Lowercase hex HMAC-SHA256 of the exact request body, keyed with your signature
WebHook-Signature-Version v1
Webhook-Delivery-Retry The attempt: 0 on the first try, up to 10
client-request-id The Client-Request-Id of the API call that caused the change, if it sent one

HTTP header names are case-insensitive, so read them in any case. To check a delivery without writing code, paste it into the signature checker; it runs in your browser.

Delivery and retries

Rule Value
Success Any 2xx within 10 seconds
Retries Up to 10 retries, 11 attempts, over about 24 hours, with exponential backoff
Disabled at once Your endpoint answers 403 or 410
Disabled later Retries run out, or a 404 on the last attempt
Order Not guaranteed; each chunk retries on its own
On delete Deleting a webhook sends DELETE to its URL; accept it

A disabled webhook disappears without notice. The details, and what to do about it, are on delivery and retries.

Avoid loops

If your integration writes back to Coevera, a webhook would report your own writes to you. Send the header WebHook-Skip-Key: northwind-sync on those API calls, and list the same key in the webhook's options.skip_keys: the webhook does not fire for those writes. The update below adds the key.

Update, list and delete

Update with PATCH (REST) or updateWebhook (GraphQL). The events and options you send replace the webhook's, so send the whole list and the whole object:

# The events and options you send replace the webhook's: send the whole list and object.
curl -sS --fail-with-body -X PATCH "$COEVERA_BASE/entities/Webhooks/$WEBHOOK_ID" \
  -u "$COEVERA_API_KEY:$COEVERA_API_SECRET" \
  -H "Content-Type: application/json" \
  -d @- <<JSON
{
  "events": ["Opportunity.Won", "Opportunity.Lost", "Opportunity.Move"],
  "options": {
    "entity": {
      "Opportunity": { "load-only": ["id", "name", "owner_id", "step_id", "value", "revision"] }
    },
    "chunk_size": 50,
    "skip_keys": ["northwind-sync"]
  }
}
JSON
echo
// The events and options you send replace the webhook's: send the whole list and object.
await coevera("PATCH", `/entities/Webhooks/${webhookId}`, {
  events: ["Opportunity.Won", "Opportunity.Lost", "Opportunity.Move"],
  options: {
    entity: { Opportunity: { "load-only": ["id", "name", "owner_id", "step_id", "value", "revision"] } },
    chunk_size: 50,
    skip_keys: ["northwind-sync"],
  },
});
# The events and options you send replace the webhook's: send the whole list and object.
coevera(
    "PATCH",
    f"/entities/Webhooks/{webhook_id}",
    json={
        "events": ["Opportunity.Won", "Opportunity.Lost", "Opportunity.Move"],
        "options": {
            "entity": {"Opportunity": {"load-only": ["id", "name", "owner_id", "step_id", "value", "revision"]}},
            "chunk_size": 50,
            "skip_keys": ["northwind-sync"],
        },
    },
)
# The events and options you send replace the webhook's: send the whole list and object.
jq -n --arg id "$WEBHOOK_ID" '{
  query: "mutation UpdateWebhook($input: UpdateWebhookInput!) { updateWebhook(input: $input) { webhook { id events options } } }",
  variables: {
    input: {
      id: $id,
      events: ["OpportunityWon", "OpportunityLost", "OpportunityMove"],
      options: ({
        entity: { Opportunity: { "load-only": ["id", "name", "owner_id", "step_id", "value", "revision"] } },
        chunk_size: 50,
        skip_keys: ["northwind-sync"]
      } | tojson)
    }
  }
}' | gql
// The events and options you send replace the webhook's: send the whole list and object.
await gql(
  `mutation UpdateWebhook($input: UpdateWebhookInput!) {
    updateWebhook(input: $input) { webhook { id events options } }
  }`,
  {
    input: {
      id: webhookId,
      events: ["OpportunityWon", "OpportunityLost", "OpportunityMove"],
      options: JSON.stringify({
        entity: { Opportunity: { "load-only": ["id", "name", "owner_id", "step_id", "value", "revision"] } },
        chunk_size: 50,
        skip_keys: ["northwind-sync"],
      }),
    },
  },
);
# The events and options you send replace the webhook's: send the whole list and object.
gql(
    """
    mutation UpdateWebhook($input: UpdateWebhookInput!) {
      updateWebhook(input: $input) { webhook { id events options } }
    }
    """,
    {
        "input": {
            "id": webhook_id,
            "events": ["OpportunityWon", "OpportunityLost", "OpportunityMove"],
            "options": json.dumps({
                "entity": {"Opportunity": {"load-only": ["id", "name", "owner_id", "step_id", "value", "revision"]}},
                "chunk_size": 50,
                "skip_keys": ["northwind-sync"],
            }),
        }
    },
)

Read it back with a list call. List webhooks without load-only: with it, options comes back as {}.

# Read webhooks without load-only: with it, options comes back empty. Every read also
# returns the signature, so keep these responses out of logs.
curl -sS --fail-with-body "$COEVERA_BASE/entities/Webhooks" \
  -u "$COEVERA_API_KEY:$COEVERA_API_SECRET" |
  jq '[.data[] | {id, url, events, options}]'
// Read webhooks without load-only: with it, options comes back empty. Every read also
// returns the signature, so keep these responses out of logs.
const list = await coevera("GET", "/entities/Webhooks");
const shown = list.data.map(({ id, url, events, options }: Record<string, unknown>) => ({ id, url, events, options }));
console.log(JSON.stringify(shown, null, 2));
# Read webhooks without load-only: with it, options comes back empty. Every read also
# returns the signature, so keep these responses out of logs.
webhooks = coevera("GET", "/entities/Webhooks")
shown = [{key: w[key] for key in ("id", "url", "events", "options")} for w in webhooks["data"]]
print(json.dumps(shown, indent=2))
jq -n '{
  query: "{ entities { webhook { getByCriteria(first: 10) { totalCount edges { node { id url events options } } } } } }"
}' | gql
const list = await gql(`{
  entities {
    webhook {
      getByCriteria(first: 10) { totalCount edges { node { id url events options } } }
    }
  }
}`);
console.log(JSON.stringify(list.entities.webhook.getByCriteria, null, 2));
listed = gql("""
{
  entities {
    webhook {
      getByCriteria(first: 10) { totalCount edges { node { id url events options } } }
    }
  }
}
""")
print(json.dumps(listed["entities"]["webhook"]["getByCriteria"], indent=2))
200 OKRecorded response · 2026-10-15 · docs sandbox, de-identified
{
  "success": true,
  "total": 1,
  "page_info": {
    "start_cursor": "WyIxZTAwMDAwMC0wMDAwLTQwMDAtYTAwMC0wMDAwMDAwMDAwMDEiXQ==",
    "end_cursor": "WyIxZTAwMDAwMC0wMDAwLTQwMDAtYTAwMC0wMDAwMDAwMDAwMDEiXQ==",
    "has_previous_page": false,
    "has_next_page": false
  },
  "data": [
    {
      "is_delete_protected": false,
      "id": "1e000000-0000-4000-a000-000000000001",
      "is_deleted": false,
      "modified": "2026-10-15 09:00:00.145616+00:00",
      "created": "2026-10-15 09:00:00.000035+00:00",
      "application": "https://eu-central.api.coevera.com/api/v100/rest/spaces/nv2_northwind_docs/entities/Applications/00000000-0000-4000-a000-000000000001",
      "client": null,
      "application_id": "00000000-0000-4000-a000-000000000001",
      "client_id": null,
      "insecure_ssl": false,
      "options": {
        "entity": {
          "Opportunity": {
            "load-only": [
              "id",
              "name",
              "owner_id",
              "step_id",
              "value",
              "revision"
            ]
          }
        },
        "skip_keys": [
          "northwind-sync"
        ],
        "chunk_size": 50
      },
      "signature": "1e000000-0000-4000-a000-000000000002",
      "url": "https://hooks.example.com/coevera",
      "events": [
        "Opportunity.Move",
        "Opportunity.Lost",
        "Opportunity.Won"
      ]
    }
  ]
}
Show all 49 linesShow less
200 OKRecorded response · 2026-10-15 · docs sandbox, de-identified
{
  "data": {
    "entities": {
      "webhook": {
        "getByCriteria": {
          "totalCount": 1,
          "edges": [
            {
              "node": {
                "id": "1e000000-0000-4000-a000-000000000001",
                "url": "https://hooks.example.com/coevera",
                "events": [
                  "OpportunityWon",
                  "OpportunityLost",
                  "OpportunityMove"
                ],
                "options": "{\"entity\": {\"Opportunity\": {\"load-only\": [\"id\", \"name\", \"owner_id\", \"step_id\", \"value\", \"revision\"]}}, \"skip_keys\": [\"northwind-sync\"], \"chunk_size\": 50}"
              }
            }
          ]
        }
      }
    }
  }
}
Show all 25 linesShow less

Delete it. Coevera then sends DELETE to the webhook's URL, so your receiver should answer it with a 2xx:

curl -sS --fail-with-body -X DELETE "$COEVERA_BASE/entities/Webhooks/$WEBHOOK_ID" \
  -u "$COEVERA_API_KEY:$COEVERA_API_SECRET"
echo
await coevera("DELETE", `/entities/Webhooks/${webhookId}`);
coevera("DELETE", f"/entities/Webhooks/{webhook_id}")
jq -n --arg id "$WEBHOOK_ID" '{
  query: "mutation DeleteWebhook($input: DeleteWebhookInput!) { deleteWebhook(input: $input) { webhook { id isDeleted } } }",
  variables: { input: { id: $id } }
}' | gql
await gql(
  `mutation DeleteWebhook($input: DeleteWebhookInput!) {
    deleteWebhook(input: $input) { webhook { id isDeleted } }
  }`,
  { input: { id: webhookId } },
);
gql(
    """
    mutation DeleteWebhook($input: DeleteWebhookInput!) {
      deleteWebhook(input: $input) { webhook { id isDeleted } }
    }
    """,
    {"input": {"id": webhook_id}},
)
200 OKRecorded response · 2026-10-15 · docs sandbox, de-identified
{
  "success": true,
  "data": "1e000000-0000-4000-a000-000000000001"
}
200 OKRecorded response · 2026-10-15 · docs sandbox, de-identified
{
  "data": {
    "deleteWebhook": {
      "webhook": {
        "id": "1e000000-0000-4000-a000-000000000001",
        "isDeleted": true
      }
    }
  }
}

The API soft-deletes: the webhook leaves the list, and include-deleted=true still shows it (querying).

# A deleted webhook is gone from the list (total: 0); include-deleted=true would still show it.
curl -sS --fail-with-body -G "$COEVERA_BASE/entities/Webhooks" \
  --data-urlencode "filter[id]=$WEBHOOK_ID" \
  -d load-only=id \
  -u "$COEVERA_API_KEY:$COEVERA_API_SECRET"
echo
// A deleted webhook is gone from the list (total: 0); include-deleted=true would still show it.
const left = await coevera("GET", `/entities/Webhooks?filter[id]=${encodeURIComponent(webhookId)}&load-only=id`);
console.log("Webhooks listed with this id:", left.total);
# A deleted webhook is gone from the list (total: 0); include-deleted=true would still show it.
left = coevera("GET", "/entities/Webhooks", params={"filter[id]": webhook_id, "load-only": "id"})
print("Webhooks listed with this id:", left.get("total"))
# A deleted webhook is gone from the list (totalCount: 0); includeDeleted: true would still show it.
jq -n --arg id "$WEBHOOK_ID" '{
  query: "query Left($id: String!) { entities { webhook { getByCriteria(filter: { id: { eq: [$id] } }) { totalCount } } } }",
  variables: { id: $id }
}' | gql
// A deleted webhook is gone from the list (totalCount: 0); includeDeleted: true would still show it.
const left = await gql(
  `query Left($id: String!) {
    entities { webhook { getByCriteria(filter: { id: { eq: [$id] } }) { totalCount } } }
  }`,
  { id: webhookId },
);
console.log("Webhooks listed with this id:", left.entities.webhook.getByCriteria.totalCount);
# A deleted webhook is gone from the list (totalCount: 0); includeDeleted: true would still show it.
left = gql(
    """
    query Left($id: String!) {
      entities { webhook { getByCriteria(filter: { id: { eq: [$id] } }) { totalCount } } }
    }
    """,
    {"id": webhook_id},
)
print("Webhooks listed with this id:", left["entities"]["webhook"]["getByCriteria"]["totalCount"])
200 OKRecorded response · 2026-10-15 · docs sandbox, de-identified
{
  "success": true,
  "total": 0,
  "page_info": {
    "start_cursor": null,
    "end_cursor": null,
    "has_previous_page": false,
    "has_next_page": false
  },
  "data": []
}
200 OKRecorded response · 2026-10-15 · docs sandbox, de-identified
{
  "data": {
    "entities": {
      "webhook": {
        "getByCriteria": {
          "totalCount": 0
        }
      }
    }
  }
}

Limits

  • Webhooks are managed through the API only; the CRM has no screen for them.
  • At most 100 events per delivery (chunk_size, 1 to 100).
  • There is no test event, delivery log or replay in Coevera; test with your own deliveries (testing locally).
  • There is no notification when a webhook is disabled.
  • The Automatizer Send Webhook action is a separate system with its own rules; see API webhooks vs Automatizer webhooks.

Next steps