ChatKomodo Docs Home Sign in

API reference · Integrations

Webhooks

Endpoints that manage your outbound webhooks. The webhooks guide explains events, signatures and retries.

List webhooks

GET/v1/webhooks

List webhooks for the calling tenant.

Responses

  • 200List
    Response fields
    • webhooksarray of objectrequired
    • webhooks[].idstringrequired
    • webhooks[].urlstring (uri)required
    • webhooks[].eventsarray of stringrequired
    • webhooks[].created_atintegerrequired
    • webhooks[].disabled_atinteger | nullrequired
Request
curl "https://api.chatkomodo.com/v1/webhooks" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/webhooks', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
  },
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);
import os
import requests

res = requests.get(
    "https://api.chatkomodo.com/v1/webhooks",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    timeout=30,
)
res.raise_for_status()
print(res.json())
<?php
require 'vendor/autoload.php';

$client = new GuzzleHttp\Client();
$res = $client->request('GET', 'https://api.chatkomodo.com/v1/webhooks', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "webhooks": [
    {
      "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "url": "https://example.com/file.jpg",
      "events": [
        "message.received"
      ],
      "created_at": 1767225600000,
      "disabled_at": 1767225600000
    }
  ]
}

Create a webhook

POST/v1/webhooks

Register an outbound webhook. URL must be HTTPS, secret must be ≥32 chars. HMAC-SHA256 signed.

Request body

  • urlstring (uri)required
    Up to 512 characters
  • secretstringrequired
    32–256 characters
  • eventsarray of stringrequired

Responses

  • 201Webhook registered
    Response fields
    • idstringrequired
    • urlstring (uri)required
    • eventsarray of stringrequired
    • created_atintegerrequired
    • disabled_atinteger | nullrequired
  • 400Validation error
Request
curl -X POST "https://api.chatkomodo.com/v1/webhooks" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://example.com/webhooks/chatkomodo",
  "secret": "a-long-random-secret-of-32-characters-or-more",
  "events": [
    "message.received",
    "message.failed",
    "session.disconnected"
  ]
}'
const res = await fetch('https://api.chatkomodo.com/v1/webhooks', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "url": "https://example.com/webhooks/chatkomodo",
    "secret": "a-long-random-secret-of-32-characters-or-more",
    "events": [
      "message.received",
      "message.failed",
      "session.disconnected"
    ]
  }),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);
import os
import requests

res = requests.post(
    "https://api.chatkomodo.com/v1/webhooks",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "url": "https://example.com/webhooks/chatkomodo",
        "secret": "a-long-random-secret-of-32-characters-or-more",
        "events": [
            "message.received",
            "message.failed",
            "session.disconnected",
        ],
    },
    timeout=30,
)
res.raise_for_status()
print(res.json())
<?php
require 'vendor/autoload.php';

$client = new GuzzleHttp\Client();
$res = $client->request('POST', 'https://api.chatkomodo.com/v1/webhooks', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'url' => 'https://example.com/webhooks/chatkomodo',
        'secret' => 'a-long-random-secret-of-32-characters-or-more',
        'events' => [
            'message.received',
            'message.failed',
            'session.disconnected',
        ],
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 201
{
  "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
  "url": "https://example.com/file.jpg",
  "events": [
    "message.received"
  ],
  "created_at": 1767225600000,
  "disabled_at": 1767225600000
}

List webhook deliveries

GET/v1/webhooks/deliveries

Delivery log, newest first: one row per event and webhook, updated on every attempt. Use it to see why an endpoint is not receiving events.

Query parameters

  • limitinteger
    Rows to return; values outside 1-200 are clamped
    Default: 50From 1 to 200
  • webhook_idstring
    Only deliveries of this webhook
    Up to 64 characters

Responses

  • 200Deliveries
    Response fields
    • deliveriesarray of objectrequired
    • deliveries[].idstringrequired
    • deliveries[].webhook_idstringrequired
    • deliveries[].urlstringrequired
      Endpoint URL at the time of delivery
    • deliveries[].eventstringrequired
      Event type, e.g. message.received or webhook.test
    • deliveries[].statusstringrequired
      retrying while attempts remain, failed once they are exhausted
      One of: delivered, failed, retrying
    • deliveries[].attemptsintegerrequired
    • deliveries[].response_statusinteger | nullrequired
      HTTP status returned by the endpoint on the last attempt, or null if it never answered
    • deliveries[].errorstring | nullrequired
      Short reason for the last failure: HTTP <status>, timeout, blocked_destination, dns_error, connection_refused or network_error
    • deliveries[].created_atintegerrequired
      Unix time in milliseconds
    • deliveries[].delivered_atinteger | nullrequired
      When the delivery succeeded, or null
  • 401Missing, invalid or expired credentials
  • 403Forbidden for a customer-service agent login (Agent access is not allowed for this operation) and an API key restricted to specific sessions (KEY_SCOPE_FORBIDDEN)
Request
curl "https://api.chatkomodo.com/v1/webhooks/deliveries?limit=50" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/webhooks/deliveries?limit=50', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
  },
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);
import os
import requests

res = requests.get(
    "https://api.chatkomodo.com/v1/webhooks/deliveries?limit=50",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    timeout=30,
)
res.raise_for_status()
print(res.json())
<?php
require 'vendor/autoload.php';

$client = new GuzzleHttp\Client();
$res = $client->request('GET', 'https://api.chatkomodo.com/v1/webhooks/deliveries?limit=50', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "deliveries": [
    {
      "id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "webhook_id": "01JA5V6Q8M2H7G4K9P3T0R1S2X",
      "url": "https://example.com/file.jpg",
      "event": "message.received",
      "status": "delivered",
      "attempts": 1,
      "response_status": 1,
      "error": "string",
      "created_at": 1767225600000,
      "delivered_at": 1767225600000
    }
  ]
}

Update a webhook

PATCH/v1/webhooks/{id}

Changes the URL, secret, event list and/or the paused state of a webhook. A new URL goes through the same HTTPS and private-address checks as on creation. Send only the fields to change.

Path parameters

  • idstringrequired
    Webhook id

Request body

  • urlstring (uri)
    Up to 512 characters
  • secretstring
    New signing secret, at least 32 characters. Write-only: it is never returned
    32–256 characters
  • eventsarray of string
    Event types to deliver (1-50 entries, each up to 64 characters)
    1–50 items
  • disabledboolean
    true pauses deliveries, false resumes them

Responses

  • 200Updated
    Response fields
    • okbooleanrequired
  • 400Validation failed (VALIDATION_FAILED, INVALID_JSON) or the URL points at a blocked destination (WEBHOOK_URL_BLOCKED)
  • 401Missing, invalid or expired credentials
  • 403Forbidden for a customer-service agent login (Agent access is not allowed for this operation) and an API key restricted to specific sessions (KEY_SCOPE_FORBIDDEN)
  • 404Webhook not found, or it belongs to another tenant (NOT_FOUND)
Request
curl -X PATCH "https://api.chatkomodo.com/v1/webhooks/ID" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "events": [
    "message.received",
    "message.failed"
  ],
  "disabled": false
}'
const res = await fetch('https://api.chatkomodo.com/v1/webhooks/ID', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "events": [
      "message.received",
      "message.failed"
    ],
    "disabled": false
  }),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);
import os
import requests

res = requests.patch(
    "https://api.chatkomodo.com/v1/webhooks/ID",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    json={
        "events": [
            "message.received",
            "message.failed",
        ],
        "disabled": False,
    },
    timeout=30,
)
res.raise_for_status()
print(res.json())
<?php
require 'vendor/autoload.php';

$client = new GuzzleHttp\Client();
$res = $client->request('PATCH', 'https://api.chatkomodo.com/v1/webhooks/ID', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
    'json' => [
        'events' => [
            'message.received',
            'message.failed',
        ],
        'disabled' => false,
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "ok": true
}

Delete a webhook

DEL/v1/webhooks/{id}

Permanently deletes a webhook; no further events are delivered to it.

Path parameters

  • idstringrequired
    Webhook id

Responses

  • 200Deleted
    Response fields
    • okbooleanrequired
  • 401Missing, invalid or expired credentials
  • 403Forbidden for a customer-service agent login (Agent access is not allowed for this operation) and an API key restricted to specific sessions (KEY_SCOPE_FORBIDDEN)
  • 404Webhook not found, or it belongs to another tenant (NOT_FOUND)
Request
curl -X DELETE "https://api.chatkomodo.com/v1/webhooks/ID" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/webhooks/ID', {
  method: 'DELETE',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
  },
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);
import os
import requests

res = requests.delete(
    "https://api.chatkomodo.com/v1/webhooks/ID",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    timeout=30,
)
res.raise_for_status()
print(res.json())
<?php
require 'vendor/autoload.php';

$client = new GuzzleHttp\Client();
$res = $client->request('DELETE', 'https://api.chatkomodo.com/v1/webhooks/ID', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "ok": true
}

Send a test event to a webhook

POST/v1/webhooks/{id}/test

Sends a signed webhook.test event to the webhook URL (10 second timeout) and reports the outcome. The attempt is recorded in the delivery log and is never retried. An unreachable or failing endpoint still returns HTTP 200 with ok: false.

Path parameters

  • idstringrequired
    Webhook id

Responses

  • 200Outcome of the delivery attempt
    Response fields
    • okbooleanrequired
      true when the endpoint answered with a 2xx status
    • statusinteger | nullrequired
      HTTP status returned by the endpoint, or null when it could not be reached
    • messagestringrequired
      Human-readable result, e.g. Delivered or Endpoint responded HTTP 500
  • 401Missing, invalid or expired credentials
  • 403Forbidden for a customer-service agent login (Agent access is not allowed for this operation) and an API key restricted to specific sessions (KEY_SCOPE_FORBIDDEN)
  • 404Webhook not found, or it belongs to another tenant (NOT_FOUND)
Request
curl -X POST "https://api.chatkomodo.com/v1/webhooks/ID/test" \
  -H "Authorization: Bearer $CHATKOMODO_API_KEY"
const res = await fetch('https://api.chatkomodo.com/v1/webhooks/ID/test', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CHATKOMODO_API_KEY}`,
  },
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
console.log(data);
import os
import requests

res = requests.post(
    "https://api.chatkomodo.com/v1/webhooks/ID/test",
    headers={
        "Authorization": f"Bearer {os.environ['CHATKOMODO_API_KEY']}",
    },
    timeout=30,
)
res.raise_for_status()
print(res.json())
<?php
require 'vendor/autoload.php';

$client = new GuzzleHttp\Client();
$res = $client->request('POST', 'https://api.chatkomodo.com/v1/webhooks/ID/test', [
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('CHATKOMODO_API_KEY'),
    ],
]);

print_r(json_decode((string) $res->getBody(), true));
Response 200
{
  "ok": true,
  "status": 1,
  "message": "Hi Sam, your order has shipped."
}