Agent skill

Tidio Webhooks Skill

Receive and verify Tidio webhooks. Use when setting up Tidio webhook handlers, debugging x-tidio-signature verification, or handling contact.created, conversation.solved_by_lyro, ticket.created, and customer_satisfaction.rated live chat, Lyro chatbot, and ticketing events.

Install this skill

npx skills add hookdeck/webhook-skills --skill tidio-webhooks


When to Use This Skill

  • How do I receive Tidio webhooks?
  • How do I verify the x-tidio-signature header on a Tidio webhook?
  • How do I handle contact.created, ticket.created, or conversation.solved_by_lyro events?
  • Why is my Tidio webhook signature verification failing?
  • How do I dedupe Tidio webhooks (webhook_id) and handle retries?

Verification (core)

Tidio has no webhook SDK; verify manually. The header is x-tidio-signature: t=<unix seconds>,s=<hex>[,s=<hex>...] (one s per active webhook secret). The signature is hex HMAC-SHA256 over the raw body bytes, a literal _, then the t value, keyed with the secret's UTF-8 bytes. The request is valid when any s matches.

const crypto = require('crypto');

function verifyTidio(rawBody /* Buffer */, header, secret) {
  let t;
  const sigs = [];
  for (const item of (header || '').split(',')) {
    const i = item.indexOf('=');
    if (i === -1) continue;
    const key = item.slice(0, i), value = item.slice(i + 1);
    if (key === 't' && t === undefined) t = value;
    else if (key === 's') sigs.push(value);
  }
  if (!t || sigs.length === 0) return false;
  const signed = Buffer.concat([rawBody, Buffer.from('_'), Buffer.from(t, 'latin1')]);
  const expected = Buffer.from(
    crypto.createHmac('sha256', secret).update(signed).digest('hex'));
  return sigs.some((s) =>
    /^[0-9a-fA-F]{64}$/.test(s) &&
    crypto.timingSafeEqual(Buffer.from(s.toLowerCase()), expected));
}

Gotchas that cause most failures:

  • Use the raw body (express.raw({ type: '*/*' }), request.arrayBuffer(), await request.body()). Never JSON.parse / JSON.stringify first.
  • The body comes first, then _, then t (not t.body).
  • Validate each s (exactly 64 hex characters) before comparing in constant time. Hex is case-insensitive.
  • Missing header, missing t or s, malformed signature, or mismatch: return 401.
  • No timestamp window by default. Hookdeck retries and replays resend the original headers and timestamp, so a fixed window would reject legitimate deliveries. The examples offer an opt-in TIDIO_WEBHOOK_TOLERANCE_SECONDS (t is Unix seconds; unset or 0 = off).

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

Envelope and Event Types

Every delivery is an HTTP POST with JSON { created_at, project_public_key, topic, version, webhook_id, content }. Dispatch on topic; the event data is in content.

topicTriggered When
contact.created / contact.updatedA contact is created or updated
conversation.solved_by_operatorAn operator marks a conversation as solved
conversation.solved_by_lyroLyro (AI) considers the conversation finished (reason: handoff, taken_over_by_operator, inactive)
conversation.solved_automaticallyThe Automatic Solve feature resolves a conversation
conversation.operator_repliedAn operator message is persisted on the chat channel
ticket.created / ticket.status_updatedA ticket is created or its status changes
ticket.operator_assigned / ticket.holder_changedA ticket gets a new operator or holder
ticket.operator_replied / ticket.contact_replied / ticket.internal_note_added / ticket.repliedReplies and notes on a ticket (ticket.replied covers all of them)
customer_satisfaction.ratedA customer satisfaction survey is submitted

Changes made through the Tidio OpenAPI do not trigger webhooks. See references/overview.md for payloads.

Delivery Behaviour

  • Respond 2xx within 4 seconds; process asynchronously.
  • Non-2xx, timeout, or connection error: retried 9 times (5 s, 10 s, 30 s, 1 min, 3 min, 10 min, 30 min, 1 h, 3 h; about 4 h 45 min total).
  • Ordering is not guaranteed (order by created_at); duplicates are possible, so dedupe on webhook_id.

Environment Variables

TIDIO_WEBHOOK_SECRET=your_tidio_webhook_secret   # Settings > Developer > Webhooks
TIDIO_WEBHOOK_TOLERANCE_SECONDS=0                # optional opt-in replay window; 0 or unset = off

Local Development

# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 tidio --path /webhooks/tidio

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 11, 2026

View on GitHub →