Support toolsAgent copilot

Provide dynamic information to the copilot using a prehook

Overview

Sometimes you may want to provide information from external systems to the Inkeep support copilot. This can include:

  • Payment information from your billing system
  • Account information from your CRM
  • Activity or product-specific data from your database
  • Uptime information from your application monitoring software

To accomplish this, you can use a prehook for the copilot. When a prehook is enabled, the following happens:

  1. When a support agent clicks on Smart Assist, Inkeep will make a request to an API route of your choosing.
  2. Your API route fetches information from any arbitrary backend service[s].
  3. Your API route returns that information and any custom instructions in the API route's response.
  4. That information is added as custom context for the AI copilot to consider.
  5. That context is persisted across the entire conversation with the copilot.

Think of the prehook as a way to inject dynamic, custom data or instructions into the Copilot's decision-making process.

1. Deploy your API route

You'll need to deploy an API endpoint that the Copilot can call.

A quick way to deploy is to use the below Vercel template: Deploy with Vercel

  1. Click the "Deploy with Vercel" button above
  2. Connect your GitHub account when prompted
  3. Generate a random API key using this command:
    openssl rand -base64 30 | cut -c1-40
  4. Enter this API key as the INKEEP_SUPPORT_COPILOT_API_KEY environment variable
  5. Click Deploy

Option 2: Deploy anywhere

As long as your API route follows the API contract, you can deploy and develop the API route in any cloud provider or API framework. A reference TypeScript template repository is provided here.

2. Write your custom logic

Below are the schemas that are used by the prehook. If you're using the reference template, you can customize your business logic in this file.

Request

The request to your API endpoint will include information the Copilot has in relation to the current ticket. You can use this information, like a user email or ID, to fetch information from downstream services.

Request examples

// POST /your-route
 
Headers: {
	host: 'your-endpoint-url',
	'content-type': 'application/json',
	authorization: 'Bearer INKEEP_SUPPORT_COPILOT_API_KEY',
}
 
Body: {
	ticketId: '120',
	ticketAttributes: {
  // Standard ticket fields
		ticketingPlatformType: 'zendesk',
		ticketId: '120',
		tags: [ 'tag1', 'tag2', 'tag3' ],
		priority: 'normal',
		type: 'task',
 
  // Custom ticket fields, where the key is custom_field_{field_id}. See the field ID in your zendesk ticket fields setup to use the correct key.
		'custom_field_33823202294419': 'Text value',
		'custom_field_37074111502483': 'Text value line 1\nText value line 2',
		'custom_field_37074153082259': true,
		'custom_field_37074157971731': '1234',
		'custom_field_37074158858643': '2025-01-01', // date field
		'custom_field_37074163782803': '117', // dropdown field, this is the ID of the ticket/organization/user selected
		'custom_field_37076344188179': [ 'q1', 'q4' ] // multiselect field values are an array of strings
	},
	userAttributes: {
  // Standard user fields
		id: 11122233344455,
		url: 'https://<subdomain>.zendesk.com/api/v2/users/11122233344455.json',
		name: 'User Name',
		email: 'user@example.com',
		created_at: '2024-11-07T08:02:58Z',
		updated_at: '2025-01-06T19:35:33Z',
		time_zone: 'Pacific Time (US & Canada)',
		iana_time_zone: 'America/Los_Angeles',
		locale_id: 1,
		locale: 'en-US',
		organization_id: 13457869872341,
		role: 'end-user',
		tags: [ 'user-tag1', 'user-tag2', 'user-tag3' ],
		active: true,
		ticket_restriction: 'requested',
		restricted_agent: true,
 
  // Custom user fields, where the key is the field key from your zendesk user fields setup
		custom_user_dropdown_field: 'dropdown value',
		custom_lookup_rel_field_to_org: '13457869872341', // this is the ID of the organization selected in the dropdown field
		custom_user_number_field: 4321,
		custom_user_text_field: 'text value'
	},
	organizationAttributes: {
  // Standard organization fields
		id: 13457869872341,
		name: 'Test Org',
		tags: [ 'org_tag' ],
 
  // Custom organization fields, where the key is the field key from your zendesk organization fields setup
		custom_organization_date_field: '2025-01-01T00:00:00+00:00',
		custom_organization_text_field: 'text field'
	},
	messages: [
		{
			id: '120',
			createdAt: '2024-11-07T08:02:59.000Z',
			content: 'How do I add Inkeep to a Slack workspace?',
			authorId: '11122233344455',
			authorType: 'user',
			authorName: 'User Name',
			files: []
		},
		{
			id: '35171330444691',
			createdAt: '2024-11-07T08:03:59.000Z',
			content: 'I want to add Inkeep as an autoreply bot.',
			authorId: '11122233344455',
			authorType: 'user',
			authorName: 'User Name',
			files: [],
			isInternalComment: false
		},
		{
			id: '35171348277779',
			createdAt: '2024-11-07T08:04:59.000Z',
			content: 'Inkeep offers auto-reply functionality for both Slack and Discord:\n' +
				'Slack Auto-Reply\n' +
				'- Configure the bot to automatically reply to all root-level messages in specific channels(1)\n' +
				'...',
			authorId: '35089669796243',
			authorType: 'member',
			authorName: 'AI Agent',
			files: [],
			isInternalComment: false
		}
	],
	ticketingPlatformType: 'zendesk'
}

Request parameters

ParameterTypeDescription
ticketIdstringUnique identifier for the ticket for your support platform.
ticketingPlatformTypestringThe support platform type ("zendesk", "github", "plain", or "other").
ticketAttributesobjectInformation about the ticket provided by the support platform.
userAttributesobjectInformation about the user provided by the support platform.
organizationAttributesobjectInformation about the organization the user is a member of as provided by the support platform (for business-to-business type scenarios).
messagesMessageObject[]List of message objects containing conversation history.
Note
Note

The exact ticket, user and organization attributes provided are dependent on the support ticketing platform. If you'd like to fetch more information from the support platform, you can use the ticketId to query relevant information.

MessageObject

ParameterTypeDescription
idstringUnique identifier for the message.
createdAtdate (optional)Timestamp when the message was created.
contentstringThe message text content.
authorIdstringUnique identifier for the message author.
authorTypestringType of author ("user" or "member").
authorNamestring (optional)Name of the message author.
filesarrayList of attached files, each containing id and url.
isInternalCommentboolean (optional)Whether the message is an internal note.

Response

The response from your API endpoint should include any additional context you want to provide to the Copilot. You can also include specific instructions to guide how the Copilot should respond. All of the below are Optional.

Here's an example response:

{
  "userAttributes": [{
    "label": "payment_status",
    "value": "overdue",
    "description": "Customer has an overdue payment",
    "useWhen": "when user asks about billing issues",
  }],
  "organizationAttributes": [{
    "label": "support_tier",
    "value": "enterprise",
    "description": "Organization has enterprise support",
	"audience": "member"
  }],
  "prompt": "This is an enterprise customer, ensure you use a warm, expedient tone that reflects that we are urgently considering their request. There is currently an outage on the dashboard, so if they mention that, let them know this the team is aware and actively investigating."
}

Response format

ParameterTypeDescription
userAttributesAttributeObject[]Optional. List of attribute objects providing context about the user.
organizationAttributesAttributeObject[]Optional. List of attribute objects providing context about the organization.
ticketAttributesAttributeObject[]Optional. List of attribute objects providing context about the ticket.
promptstringOptional. Additional instructions to guide the Copilot's response.
Tip
Tip

The prompt field is best used for high-level instructions or context that doesn't fit naturally into the attributes. For example, prompt can include:

  • Real-time information on a wide-application situation: "There is currently an outage affecting our dashboard. If the user references an outage, link them to the tracker page here."
  • Tone/style guidance: "This is a VIP customer, use an expedited tone..."
  • Complex business rules: "If the user mentions feature X, first verify their subscription tier..."

Think of this as dynamic instructions or context for the bot beyond what can be described with attributes.

AttributeObject

FieldTypeDescription
labelstringSemantic, short identifier for the attribute (e.g., 'subscription_plan').
valuestringThe attribute's value (e.g., 'premium').
descriptionstringOptional explanation of the attribute's meaning or implications of the value.
useWhenstringOptional condition for when the copilot should consider or use this attribute.
audience"user" | "member" | "any"Optional field to specify which type of participant can see this attribute. Defaults to any.
Tip
Tip

useWhen and description should contain the core details of when this attribute may or may not be relevant and how it should be used.

For more complex scenarios, use xml or markdown format to specify behavior. Check out our prompting guide for ideas.

inkeepSupportCopilotSchemas.ts
import { z } from 'zod';
import { MessageSchema } from './MessagesSerializer';
 
// NOTE: These schemas are also in the docs, and copilot-prehook-template repos.
// Any changes here should be reflected in those repos as well.
 
/* Request Schema */
/* Aim to be optimistic, the request may include more information in the future. */
 
const isNonEmpty = (val: unknown): boolean => {
  if (val === '') return false;
  if (Array.isArray(val) && val.length === 0) return false;
  if (val && typeof val === 'object' && Object.keys(val).length === 0) return false;
  return val !== undefined && val !== null;
};
 
const MessageSchema = z.object({
	id: z.string(),
	createdAt: z.preprocess(
		(arg) => (arg ? new Date(arg as string) : undefined),
		z.date().optional(),
	),
	content: z.string(),
	authorId: z.string(),
	authorType: z.enum(["user", "member"]),
	authorName: z.string().optional(),
	files: z.array(
		z.object({
			id: z.string(),
			url: z.string(),
		}),
	),
	isInternalComment: z.boolean().optional(),
});
 
export type MessageType = z.infer<typeof MessageSchema>;
 
export const RequestAttributesSchema = z.record(
  z.string(),
  z.unknown().refine(isNonEmpty, {
    message: 'Value must not be empty or null/undefined',
  }),
);
 
export const CopilotSmartAssistContextHookRequestSchema = z
  .object({
    ticketId: z.string(),
    ticketAttributes: RequestAttributesSchema,
    userAttributes: RequestAttributesSchema,
    organizationAttributes: RequestAttributesSchema,
    messages: z.array(MessageSchema),
    ticketingPlatformType: z.enum(['zendesk', 'github', 'plain', 'other']),
  })
  .passthrough();
 
const ReturnAttributeSchema = z
  .object({
    label: z.string().describe("A short identifier or name for the attribute (e.g. 'Subscription Plan')."),
    value: z
      .string()
      .describe("The attribute's value, often a string that can be displayed or processed (e.g. 'premium')."),
    description: z
      .string()
      .nullish()
      .describe(
        'An optional explanation or context about how the AI should interpret or use this attribute or particular value of the attribute.',
      ),
    useWhen: z
      .string()
      .nullish()
      .describe(
        "An optional description of when this attribute should be considered (e.g. 'when user asks about billing').",
      ),
    audience: z
      .enum([...MessageSchema.shape.authorType.options, 'any'])
      .nullish()
      .default('any')
      .describe(
        'The intended audience for this attribute. For example, use "member" for "internal" information that should not be directly referenced in messages to the user.',
      ),
  })
  .describe(
    'A structured piece of contextual data (attribute) that may be relevant to the copilot depending on the support ticket.',
  );
 
export const CopilotSmartAssistContextHookResponseSchema = z
  .object({
    userAttributes: z
      .array(ReturnAttributeSchema)
      .nullish()
      .describe('A list of user-specific attributes providing additional context about the end-user.'),
 
    organizationAttributes: z
      .array(ReturnAttributeSchema)
      .nullish()
      .describe(
        "A list of organization-specific attributes providing additional context about the user's organization or account.",
      ),
 
    ticketAttributes: z
      .array(ReturnAttributeSchema)
      .nullish()
      .describe('A list of ticket-specific attributes providing additional context about the ticket.'),
 
    prompt: z
      .string()
      .nullish()
      .describe(
        "Optional custom instructions or guidance to influence the copilot's response (e.g. special handling instructions, tone guidelines).",
      ),
  })
  .describe(
    "The schema returned by the customer-owned endpoint, providing structured context and an optional prompt to guide the copilot's responses.",
  );
 
export type CopilotSmartAssistContextHookResponse = z.infer<typeof CopilotSmartAssistContextHookResponseSchema>;

3. Configure the prehook in the Inkeep Dashboard

Once your endpoint is deployed, configure it from the Inkeep Dashboard. Creating a separate copilot integration is recommended if you want to test the prehook without affecting your main copilot. More on this in the Testing section below.

  1. Create a new Support Copilot integration in the Inkeep Dashboard. Follow the steps here and give it a name like "Support Copilot with Prehook".
  2. Expand Advanced Settings.
  3. Under Prehook URL, enter the full URL of your API endpoint. Example: https://your-app-deployment-url.vercel.app/api.
  4. Under Headers (JSON), enter the headers you want to include in the prehook request. See below.
  5. Click Save.

Once this is saved, this new Copilot will use your prehook to get additional context and instructions when it runs.

Custom headers

In the Headers (JSON) field, you can configure custom headers to be included in the prehook request. While Content-Type: application/json is always included, you can use this to add additional headers, like for authentication. The value should be a valid JSON object with double quotes.

Example: To secure the API request, you can configure the headers to include an API key. For example:

{ "Authorization": "Bearer your-api-key" }

The template implementation includes a validation for this, so your-api-key should match the value of the INKEEP_SUPPORT_COPILOT_API_KEY environment variable.

If using your own implementation, ensure you implement your own validation of relevant headers.

Note
Note
The Headers (JSON) field is encrypted at storage, so it's safe to include sensitive information like API keys.

4. Testing your prehook

Once you've configured your prehook in the Inkeep Dashboard, you can test it by updating your setup in your support platform and clicking on Smart Assist.

Developing locally with ngrok

To test your prehook locally before deploying to production, you can use ngrok to create a public URL for your local development server:

  1. Start your local development server:

    # If using the reference template:
    vercel dev   # Runs on http://localhost:3000/api
     
    # If using your own implementation:
    # Start your server using your preferred method
  2. Install and set up ngrok:

  3. Create a secure tunnel to your local server:

    # Replace 3000 with your local server's port number
    ngrok http 3000
  4. In the ngrok output, copy the HTTPS URL (e.g., https://1234-your-tunnel.ngrok.app/api)

  5. Use this URL as your prehook URL in the Inkeep Dashboard

Note
Note

Important: The ngrok URL is temporary and changes each time you restart ngrok.

Testing in your support platform

  1. Install another Zendesk app to be used for testing following the steps here, making sure to use the API key from the new "Support Copilot with Prehook" integration from the previous step.
  2. Open your test Zendesk app and click on Smart Assist. The Copilot should now use your prehook.
  3. Once you're done testing, you can update your API key in your original Zendesk App configuration with the API key from the new "Support Copilot with Prehook" integration.
  4. Disable or delete your test Zendesk app.