Open API v1

Receive and verify webhooks

TaggoAI POSTs a signed JSON event to each endpoint you register.

Required scope: webhooks:manage.

Create an endpoint

The URL must be HTTPS. The signing secret is returned once.

bash
curl -X POST https://api.taggoai.com/open/v1/webhook-endpoints \
  -H "Authorization: Bearer tg_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/taggo/webhooks",
    "events": ["contact.created", "ticket.created", "message.received"]
  }'

Store secret (whsec_...). List responses omit it.

Event catalog

EventWhen
contact.createdA contact is created
contact.updatedA contact is updated
ticket.createdA ticket is created
ticket.updatedA ticket is updated
message.receivedA customer message arrives
conversation.assignedAssignees on a conversation change

Payload

json
{
  "id": "evt_...",
  "object": "event",
  "type": "contact.created",
  "created": 1710000000,
  "data": { "id": "...", "object": "contact" }
}

Headers:

http
Taggo-Signature: t=1710000000,v1=hex_hmac
Taggo-Event: contact.created
Taggo-Delivery: evt_...

Delivery retries up to 4 times with backoff. After 15 consecutive failures the endpoint is disabled.

Verify the signature

Compute HMAC-SHA256 of {timestamp}.{raw_body} using the endpoint secret. Compare it to v1. Reject if the timestamp is older than 5 minutes.

Node.js

js
import crypto from 'crypto';

function verify(rawBody, header, secret, toleranceSec = 300) {
  const parts = Object.fromEntries(
    header.split(',').map((item) => item.split('='))
  );
  const timestamp = Number(parts.t);
  if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSec) return false;
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');
  return crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
}

Python

python
import hmac, hashlib, time

def verify(raw_body: bytes, header: str, secret: str, tolerance=300) -> bool:
    parts = dict(item.split("=", 1) for item in header.split(","))
    timestamp = int(parts["t"])
    if abs(time.time() - timestamp) > tolerance:
        return False
    expected = hmac.new(
        secret.encode(), f"{timestamp}.".encode() + raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(parts["v1"], expected)

PHP

php
function verify(string $rawBody, string $header, string $secret, int $tolerance = 300): bool {
  $parts = [];
  foreach (explode(',', $header) as $item) {
    [$k, $v] = explode('=', $item, 2);
    $parts[$k] = $v;
  }
  $timestamp = (int) $parts['t'];
  if (abs(time() - $timestamp) > $tolerance) return false;
  $expected = hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);
  return hash_equals($expected, $parts['v1']);
}