Ndoto Docs

Webhooks

Subscribe to real-time events from Ndoto

Webhooks

Webhooks let you receive real-time event notifications from Ndoto. When an event occurs — such as a new conversation or an incoming message — Ndoto sends an HTTP POST request to your configured URL with the event data.

Base path: /api/v1/accounts/:account_id/webhooks


Available events

EventWhen it fires
conversation_createdA new conversation is created
conversation_updatedA conversation's attributes change
conversation_status_changedA conversation's status changes (open, resolved, etc.)
message_createdA new message arrives in any conversation
message_updatedA message is updated
contact_createdA new contact is created
contact_updatedA contact's details change

Webhook payload

When an event fires, Ndoto sends a POST request to your URL with a JSON body:

{
  "event": "message_created",
  "id": "101",
  "content": "Hi, I need help.",
  "created_at": "2024-04-01T10:00:00.000Z",
  "message_type": "incoming",
  "conversation": {
    "id": 42,
    "status": "open",
    "inbox_id": 2
  },
  "contact": {
    "id": 10,
    "name": "Jane Doe",
    "email": "[email protected]"
  },
  "account": {
    "id": 1,
    "name": "Acme Support"
  }
}

List webhooks

Returns all webhooks configured in your account.

GET /api/v1/accounts/:account_id/webhooks

Response

{
  "payload": [
    {
      "id": 7,
      "url": "https://yourapp.com/webhooks/ndoto",
      "name": "My integration",
      "subscriptions": ["message_created", "conversation_created"],
      "active": true
    }
  ]
}
const response = await fetch(
  'https://app.usendoto.com/api/v1/accounts/1/webhooks',
  {
    headers: { 'api_access_token': 'YOUR_TOKEN' },
  }
);
const data = await response.json();
console.log(data.payload);
import requests

response = requests.get(
    'https://app.usendoto.com/api/v1/accounts/1/webhooks',
    headers={'api_access_token': 'YOUR_TOKEN'},
)
print(response.json())
<?php
$ch = curl_init('https://app.usendoto.com/api/v1/accounts/1/webhooks');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['api_access_token: YOUR_TOKEN'],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($response);
import 'package:http/http.dart' as http;
import 'dart:convert';

final response = await http.get(
  Uri.parse('https://app.usendoto.com/api/v1/accounts/1/webhooks'),
  headers: {'api_access_token': 'YOUR_TOKEN'},
);
print(jsonDecode(response.body));
require 'net/http'
require 'json'

uri = URI('https://app.usendoto.com/api/v1/accounts/1/webhooks')
req = Net::HTTP::Get.new(uri)
req['api_access_token'] = 'YOUR_TOKEN'

res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
puts JSON.parse(res.body)

Create a webhook

Registers a new webhook endpoint.

POST /api/v1/accounts/:account_id/webhooks

Body parameters

ParameterTypeRequiredDescription
urlstringYesThe URL Ndoto will POST events to
subscriptionsarrayYesList of event names to subscribe to
namestringNoA label for this webhook
inbox_idnumberNoScope events to a specific inbox
const response = await fetch(
  'https://app.usendoto.com/api/v1/accounts/1/webhooks',
  {
    method: 'POST',
    headers: {
      'api_access_token': 'YOUR_TOKEN',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      url: 'https://yourapp.com/webhooks/ndoto',
      name: 'My integration',
      subscriptions: ['message_created', 'conversation_created', 'conversation_status_changed'],
    }),
  }
);
const webhook = await response.json();
console.log(webhook);
response = requests.post(
    'https://app.usendoto.com/api/v1/accounts/1/webhooks',
    headers={'api_access_token': 'YOUR_TOKEN'},
    json={
        'url': 'https://yourapp.com/webhooks/ndoto',
        'name': 'My integration',
        'subscriptions': ['message_created', 'conversation_created', 'conversation_status_changed'],
    },
)
print(response.json())
<?php
$ch = curl_init('https://app.usendoto.com/api/v1/accounts/1/webhooks');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => json_encode([
        'url'           => 'https://yourapp.com/webhooks/ndoto',
        'name'          => 'My integration',
        'subscriptions' => ['message_created', 'conversation_created', 'conversation_status_changed'],
    ]),
    CURLOPT_HTTPHEADER => [
        'api_access_token: YOUR_TOKEN',
        'Content-Type: application/json',
    ],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($response);
final response = await http.post(
  Uri.parse('https://app.usendoto.com/api/v1/accounts/1/webhooks'),
  headers: {
    'api_access_token': 'YOUR_TOKEN',
    'Content-Type': 'application/json',
  },
  body: jsonEncode({
    'url': 'https://yourapp.com/webhooks/ndoto',
    'name': 'My integration',
    'subscriptions': ['message_created', 'conversation_created', 'conversation_status_changed'],
  }),
);
print(jsonDecode(response.body));
uri = URI('https://app.usendoto.com/api/v1/accounts/1/webhooks')
req = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json')
req['api_access_token'] = 'YOUR_TOKEN'
req.body = JSON.dump(
  url: 'https://yourapp.com/webhooks/ndoto',
  name: 'My integration',
  subscriptions: ['message_created', 'conversation_created', 'conversation_status_changed']
)

res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
puts JSON.parse(res.body)

Update a webhook

Updates an existing webhook's URL or subscriptions.

PATCH /api/v1/accounts/:account_id/webhooks/:id
const response = await fetch(
  'https://app.usendoto.com/api/v1/accounts/1/webhooks/7',
  {
    method: 'PATCH',
    headers: {
      'api_access_token': 'YOUR_TOKEN',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      subscriptions: ['message_created', 'contact_created'],
    }),
  }
);
const updated = await response.json();
console.log(updated);
response = requests.patch(
    'https://app.usendoto.com/api/v1/accounts/1/webhooks/7',
    headers={'api_access_token': 'YOUR_TOKEN'},
    json={'subscriptions': ['message_created', 'contact_created']},
)
print(response.json())
<?php
$ch = curl_init('https://app.usendoto.com/api/v1/accounts/1/webhooks/7');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PATCH',
    CURLOPT_POSTFIELDS     => json_encode([
        'subscriptions' => ['message_created', 'contact_created'],
    ]),
    CURLOPT_HTTPHEADER => [
        'api_access_token: YOUR_TOKEN',
        'Content-Type: application/json',
    ],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($response);
final response = await http.patch(
  Uri.parse('https://app.usendoto.com/api/v1/accounts/1/webhooks/7'),
  headers: {
    'api_access_token': 'YOUR_TOKEN',
    'Content-Type': 'application/json',
  },
  body: jsonEncode({'subscriptions': ['message_created', 'contact_created']}),
);
print(jsonDecode(response.body));
uri = URI('https://app.usendoto.com/api/v1/accounts/1/webhooks/7')
req = Net::HTTP::Patch.new(uri, 'Content-Type' => 'application/json')
req['api_access_token'] = 'YOUR_TOKEN'
req.body = JSON.dump(subscriptions: ['message_created', 'contact_created'])

res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
puts JSON.parse(res.body)

Delete a webhook

Removes a webhook. Ndoto will stop sending events to that URL.

DELETE /api/v1/accounts/:account_id/webhooks/:id
const response = await fetch(
  'https://app.usendoto.com/api/v1/accounts/1/webhooks/7',
  {
    method: 'DELETE',
    headers: { 'api_access_token': 'YOUR_TOKEN' },
  }
);
console.log(response.status); // 200
response = requests.delete(
    'https://app.usendoto.com/api/v1/accounts/1/webhooks/7',
    headers={'api_access_token': 'YOUR_TOKEN'},
)
print(response.status_code)
<?php
$ch = curl_init('https://app.usendoto.com/api/v1/accounts/1/webhooks/7');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'DELETE',
    CURLOPT_HTTPHEADER     => ['api_access_token: YOUR_TOKEN'],
]);
curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
final response = await http.delete(
  Uri.parse('https://app.usendoto.com/api/v1/accounts/1/webhooks/7'),
  headers: {'api_access_token': 'YOUR_TOKEN'},
);
print(response.statusCode);
uri = URI('https://app.usendoto.com/api/v1/accounts/1/webhooks/7')
req = Net::HTTP::Delete.new(uri)
req['api_access_token'] = 'YOUR_TOKEN'

res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
puts res.code

Receiving webhook events

Your webhook endpoint must:

  1. Accept POST requests
  2. Return a 2xx HTTP status code within 10 seconds
  3. Parse the JSON body to read the event data

Here's a minimal handler for each language:

// Express.js example
import express from 'express';
const app = express();
app.use(express.json());

app.post('/webhooks/ndoto', (req, res) => {
  const { event, conversation, contact, content } = req.body;

  console.log(`Event: ${event}`);
  if (event === 'message_created') {
    console.log(`New message from ${contact?.name}: ${content}`);
  }

  res.sendStatus(200);
});

app.listen(3000);
# Flask example
from flask import Flask, request

app = Flask(__name__)

@app.post('/webhooks/ndoto')
def ndoto_webhook():
    data = request.get_json()
    event = data.get('event')
    print(f"Event: {event}")

    if event == 'message_created':
        contact = data.get('contact', {})
        print(f"New message from {contact.get('name')}: {data.get('content')}")

    return '', 200
<?php
$payload = json_decode(file_get_contents('php://input'), true);
$event   = $payload['event'] ?? null;

error_log("Event: $event");

if ($event === 'message_created') {
    $name    = $payload['contact']['name'] ?? 'Unknown';
    $content = $payload['content'] ?? '';
    error_log("New message from $name: $content");
}

http_response_code(200);
import 'dart:convert';
import 'dart:io';

Future<void> main() async {
  final server = await HttpServer.bind(InternetAddress.anyIPv4, 8080);

  await for (final request in server) {
    if (request.method == 'POST' && request.uri.path == '/webhooks/ndoto') {
      final body = await utf8.decoder.bind(request).join();
      final data = jsonDecode(body) as Map<String, dynamic>;
      final event = data['event'];

      print('Event: $event');
      if (event == 'message_created') {
        final contact = data['contact'] as Map?;
        print('Message from ${contact?['name']}: ${data['content']}');
      }

      request.response.statusCode = 200;
      await request.response.close();
    }
  }
}
# Sinatra example
require 'sinatra'
require 'json'

post '/webhooks/ndoto' do
  payload = JSON.parse(request.body.read)
  event   = payload['event']

  puts "Event: #{event}"
  if event == 'message_created'
    name    = payload.dig('contact', 'name')
    content = payload['content']
    puts "New message from #{name}: #{content}"
  end

  status 200
end

On this page