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. Your AI agent or automation triggers an action (e.g. "issue refund")
- 2. Instead of executing immediately, it POSTs to Cheqpoint's inbound endpoint
- 3. A reviewer sees it in the Cheqpoint dashboard (and on Slack/Teams/Discord)
- 4. They approve, reject, or modify the payload
- 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.
Get your Connection Key
In Cheqpoint → Settings → Connection Keys, copy your workspace API key. You'll use this as the x-api-key header.
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.
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"
}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.
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");
}Intercom
Use Intercom Workflows (the visual automation builder) or webhook subscriptions to gate AI Fin actions and conversation assignments behind human approval.
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.
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)Relay the event to Cheqpoint
Your relay (or Zapier/n8n step) maps Intercom fields to the Cheqpoint payload and POSTs to the inbound endpoint.
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");
}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.
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)
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:
{
"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
expiresAtto 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.