All integrations

Integrations

Intercom & Zendesk

Gate AI-driven customer support actions — refunds, account changes, bulk emails, subscription cancellations — behind a human approval step before they execute. Works with any Intercom workflow or Zendesk Trigger, no SDK required.

How it works

  1. 1. Your AI agent or automation triggers an action (e.g. "issue refund")
  2. 2. Instead of executing immediately, it POSTs to Cheqpoint's inbound endpoint
  3. 3. A reviewer sees it in the Cheqpoint dashboard (and on Slack/Teams/Discord)
  4. 4. They approve, reject, or modify the payload
  5. 5. Cheqpoint POSTs the decision back to your webhook URL — the automation continues or stops

Zendesk

Use Zendesk Triggers or Automations to POST to Cheqpoint whenever your AI agent recommends an action on a ticket.

1

Get your Connection Key

In Cheqpoint → Settings → Connection Keys, copy your workspace API key. You'll use this as the x-api-key header.

2

Create a Zendesk Trigger

In Zendesk Admin → Objects & Rules → Triggers → Add Trigger. Set your conditions (e.g. "Tag contains ai_refund_requested") then add a Notify by webhook action pointing to Cheqpoint.

Zendesk Trigger — Webhook Action
Endpoint URL: https://app.cheqpoint.co/api/webhooks/inbound
HTTP Method:  POST
Content-Type: application/json

Headers:
  x-api-key: YOUR_CONNECTION_KEY

Body (JSON):
{
  "action":    "process_refund",
  "summary":   "Refund request on ticket #{{ticket.id}} — {{ticket.requester.name}}",
  "riskLevel": "high",
  "details": {
    "ticketId":    "{{ticket.id}}",
    "subject":     "{{ticket.title}}",
    "requester":   "{{ticket.requester.email}}",
    "amount":      "{{ticket.ticket_field_option_title_360001234567}}",
    "agentNote":   "{{ticket.latest_comment}}"
  },
  "webhookUrl": "https://yourapp.com/zendesk/decision-callback"
}
Endpoint URL: https://app.cheqpoint.co/api/webhooks/inbound
HTTP Method:  POST
Content-Type: application/json

Headers:
  x-api-key: YOUR_CONNECTION_KEY

Body (JSON):
{
  "action":    "process_refund",
  "summary":   "Refund request on ticket #{{ticket.id}} — {{ticket.requester.name}}",
  "riskLevel": "high",
  "details": {
    "ticketId":    "{{ticket.id}}",
    "subject":     "{{ticket.title}}",
    "requester":   "{{ticket.requester.email}}",
    "amount":      "{{ticket.ticket_field_option_title_360001234567}}",
    "agentNote":   "{{ticket.latest_comment}}"
  },
  "webhookUrl": "https://yourapp.com/zendesk/decision-callback"
}
3

Handle the decision callback

Cheqpoint POSTs the decision to your webhookUrl when a reviewer decides. Use this to update the Zendesk ticket via the Zendesk API.

Node.js — decision handler
import { createHmac } from "crypto";

export async function POST(req: Request) {
  const body = await req.json();

  // body.status === "APPROVED" | "REJECTED"
  // body.decisionNote — reviewer's reason
  // body.modifiedDetails — if reviewer changed the payload

  const details = body.modifiedDetails ?? body.details;

  if (body.status === "APPROVED") {
    // Execute the refund
    await stripe.refunds.create({
      amount: details.amount * 100,
      reason: "requested_by_customer",
    });

    // Update the Zendesk ticket
    await fetch(`https://yoursubdomain.zendesk.com/api/v2/tickets/${details.ticketId}`, {
      method: "PUT",
      headers: {
        Authorization: "Basic " + btoa("email@example.com/token:YOUR_ZENDESK_TOKEN"),
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        ticket: {
          status: "solved",
          comment: {
            body: `Refund of $${details.amount} approved and processed. ${body.decisionNote ?? ""}`,
            public: true,
          },
        },
      }),
    });
  } else {
    // Declined — add internal note
    await fetch(`https://yoursubdomain.zendesk.com/api/v2/tickets/${details.ticketId}`, {
      method: "PUT",
      headers: {
        Authorization: "Basic " + btoa("email@example.com/token:YOUR_ZENDESK_TOKEN"),
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        ticket: {
          comment: {
            body: `Refund declined by reviewer. Reason: ${body.decisionNote ?? "No reason given"}`,
            public: false,
          },
        },
      }),
    });
  }

  return new Response("ok");
}
import { createHmac } from "crypto";

export async function POST(req: Request) {
  const body = await req.json();

  // body.status === "APPROVED" | "REJECTED"
  // body.decisionNote — reviewer's reason
  // body.modifiedDetails — if reviewer changed the payload

  const details = body.modifiedDetails ?? body.details;

  if (body.status === "APPROVED") {
    // Execute the refund
    await stripe.refunds.create({
      amount: details.amount * 100,
      reason: "requested_by_customer",
    });

    // Update the Zendesk ticket
    await fetch(`https://yoursubdomain.zendesk.com/api/v2/tickets/${details.ticketId}`, {
      method: "PUT",
      headers: {
        Authorization: "Basic " + btoa("email@example.com/token:YOUR_ZENDESK_TOKEN"),
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        ticket: {
          status: "solved",
          comment: {
            body: `Refund of $${details.amount} approved and processed. ${body.decisionNote ?? ""}`,
            public: true,
          },
        },
      }),
    });
  } else {
    // Declined — add internal note
    await fetch(`https://yoursubdomain.zendesk.com/api/v2/tickets/${details.ticketId}`, {
      method: "PUT",
      headers: {
        Authorization: "Basic " + btoa("email@example.com/token:YOUR_ZENDESK_TOKEN"),
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        ticket: {
          comment: {
            body: `Refund declined by reviewer. Reason: ${body.decisionNote ?? "No reason given"}`,
            public: false,
          },
        },
      }),
    });
  }

  return new Response("ok");
}
No code? Use Zapier instead — trigger "Zendesk New Ticket" → "Webhooks by Zapier POST" → Cheqpoint inbound endpoint. See the Zapier guide.

Intercom

Use Intercom Workflows (the visual automation builder) or webhook subscriptions to gate AI Fin actions and conversation assignments behind human approval.

1

Subscribe to Intercom webhook events

In Intercom → Settings → Integrations → Webhooks, create a subscription. Point it at a small relay server (or an n8n/Zapier webhook) that reformats the Intercom payload for Cheqpoint.

Events to subscribe
conversation.admin.assigned
conversation.admin.replied
conversation.admin.closed
contact.tag.created          (e.g. "refund_requested" tag added by Fin)
conversation.admin.assigned
conversation.admin.replied
conversation.admin.closed
contact.tag.created          (e.g. "refund_requested" tag added by Fin)
2

Relay the event to Cheqpoint

Your relay (or Zapier/n8n step) maps Intercom fields to the Cheqpoint payload and POSTs to the inbound endpoint.

Node.js — relay handler
export async function POST(req: Request) {
  const event = await req.json();
  const convo = event.data.item;

  await fetch("https://app.cheqpoint.co/api/webhooks/inbound", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "x-api-key": process.env.CHEQPOINT_API_KEY!,
    },
    body: JSON.stringify({
      action:     "intercom_action",
      summary:    `Fin wants to ${event.topic} on conversation ${convo.id}`,
      riskLevel:  "medium",
      details: {
        conversationId: convo.id,
        contactEmail:   convo.contacts?.contacts?.[0]?.email,
        assignedAdmin:  convo.assignee?.email,
        lastMessage:    convo.conversation_parts?.conversation_parts?.slice(-1)[0]?.body,
      },
      webhookUrl: "https://yourapp.com/intercom/decision-callback",
    }),
  });

  return new Response("ok");
}
export async function POST(req: Request) {
  const event = await req.json();
  const convo = event.data.item;

  await fetch("https://app.cheqpoint.co/api/webhooks/inbound", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "x-api-key": process.env.CHEQPOINT_API_KEY!,
    },
    body: JSON.stringify({
      action:     "intercom_action",
      summary:    `Fin wants to ${event.topic} on conversation ${convo.id}`,
      riskLevel:  "medium",
      details: {
        conversationId: convo.id,
        contactEmail:   convo.contacts?.contacts?.[0]?.email,
        assignedAdmin:  convo.assignee?.email,
        lastMessage:    convo.conversation_parts?.conversation_parts?.slice(-1)[0]?.body,
      },
      webhookUrl: "https://yourapp.com/intercom/decision-callback",
    }),
  });

  return new Response("ok");
}
3

Act on the decision

When a reviewer decides in Cheqpoint, the decision is POSTed to your webhookUrl. Use the Intercom API to close the conversation, send a reply, or apply a tag.

Node.js — decision callback
export async function POST(req: Request) {
  const body = await req.json();
  const { conversationId } = body.details;

  if (body.status === "APPROVED") {
    // Close conversation and send reply to user
    await fetch(
      `https://api.intercom.io/conversations/${conversationId}/reply`,
      {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.INTERCOM_TOKEN}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          message_type: "close",
          type: "admin",
          admin_id: "YOUR_ADMIN_ID",
          body: "Your request has been approved and processed.",
        }),
      }
    );
  } else {
    // Add internal note explaining the rejection
    await fetch(
      `https://api.intercom.io/conversations/${conversationId}/reply`,
      {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.INTERCOM_TOKEN}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          message_type: "note",
          type: "admin",
          admin_id: "YOUR_ADMIN_ID",
          body: `Action declined. Reason: ${body.decisionNote ?? "No reason given"}`,
        }),
      }
    );
  }

  return new Response("ok");
}
export async function POST(req: Request) {
  const body = await req.json();
  const { conversationId } = body.details;

  if (body.status === "APPROVED") {
    // Close conversation and send reply to user
    await fetch(
      `https://api.intercom.io/conversations/${conversationId}/reply`,
      {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.INTERCOM_TOKEN}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          message_type: "close",
          type: "admin",
          admin_id: "YOUR_ADMIN_ID",
          body: "Your request has been approved and processed.",
        }),
      }
    );
  } else {
    // Add internal note explaining the rejection
    await fetch(
      `https://api.intercom.io/conversations/${conversationId}/reply`,
      {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.INTERCOM_TOKEN}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          message_type: "note",
          type: "admin",
          admin_id: "YOUR_ADMIN_ID",
          body: `Action declined. Reason: ${body.decisionNote ?? "No reason given"}`,
        }),
      }
    );
  }

  return new Response("ok");
}

Other support platforms

The same pattern works for any platform that can fire a webhook or HTTP request. Cheqpoint is platform-agnostic — map your platform's fields to the three required fields and you're done.

HubSpot

Use HubSpot Workflows → Webhook action

Freshdesk

Use Freshdesk Automation → Trigger webhook

Salesforce

Use Flow or Apex callout to POST to Cheqpoint

Minimum required payload (any platform)

JSON
POST https://app.cheqpoint.co/api/webhooks/inbound
x-api-key: YOUR_CONNECTION_KEY
Content-Type: application/json

{
  "action":    "string — what the agent wants to do",
  "summary":   "string — one sentence shown to the reviewer",
  "details":   {},
  "riskLevel": "low | medium | high",
  "webhookUrl": "https://yourapp.com/callback  (optional)"
}
POST https://app.cheqpoint.co/api/webhooks/inbound
x-api-key: YOUR_CONNECTION_KEY
Content-Type: application/json

{
  "action":    "string — what the agent wants to do",
  "summary":   "string — one sentence shown to the reviewer",
  "details":   {},
  "riskLevel": "low | medium | high",
  "webhookUrl": "https://yourapp.com/callback  (optional)"
}

Decision webhook payload

When a reviewer decides, Cheqpoint POSTs this to your webhookUrl:

JSON
{
  "id":              "req_abc123",
  "status":          "APPROVED",          // or "REJECTED"
  "details":         { ...original payload },
  "modifiedDetails": { ...reviewer edits } | null,
  "decisionNote":    "Verified — within policy",
  "decisionReasonCode": "verified_authorised",
  "decidedAt":       "2025-06-01T14:32:00.000Z",
  "decidedBy":       "alice@yourteam.com",
  "autoDecided":     false
}

// Always use modifiedDetails if set — the reviewer may have changed the amount,
// email address, or any other field before approving.
const payload = body.modifiedDetails ?? body.details;
{
  "id":              "req_abc123",
  "status":          "APPROVED",          // or "REJECTED"
  "details":         { ...original payload },
  "modifiedDetails": { ...reviewer edits } | null,
  "decisionNote":    "Verified — within policy",
  "decisionReasonCode": "verified_authorised",
  "decidedAt":       "2025-06-01T14:32:00.000Z",
  "decidedBy":       "alice@yourteam.com",
  "autoDecided":     false
}

// Always use modifiedDetails if set — the reviewer may have changed the amount,
// email address, or any other field before approving.
const payload = body.modifiedDetails ?? body.details;

Tips

  • Set riskLevel: "high" for anything involving money, data deletion, or bulk sends — it triggers PagerDuty escalation if configured.
  • Use expiresAt to auto-reject a request if no one reviews within a time window (e.g. 2 hours).
  • Set up a routing rule in Cheqpoint to send all Zendesk refund requests to a dedicated "Support" review group so only relevant teammates see them.
  • If you use Zapier as the relay, the Zapier guide has a ready-made template — the Cheqpoint inbound endpoint accepts the same payload from any source.