A guide on how to add a Canvas to Sleekplan. Canvas lets your integration render a small, interactive UI inside the Sleekplan admin (e.g., on a feedback post sidebar) and react to user input.
This document covers:
- Where Canvas appears and when it’s called
- The request payload you’ll receive
- The response schema to render UI
- How user interactions get posted back
- Security and verification
- Examples and best practices
Current availability: one Canvas location is supported — view "admin_post" (the feedback detail view) - more to come.
# How Canvas works (high level)
- In Sleekplan, a product can register one or more Canvas providers for a given view (e.g.,
admin_post). - When the view loads, Sleekplan calls your Canvas endpoint via HTTPS POST with JSON input to fetch components to render.
- When the admin interacts with your UI (e.g., clicks a button, selects an item, types and submits), Sleekplan posts the submitted values back to your endpoint and expects an updated list of components in response.
Sleekplan invokes your endpoint with JSON and includes an X-Secret header for verification.
# Where Canvas shows up
- View:
admin_post— the right sidebar within a specific feedback post in Sleekplan Admin. - Query: you’ll receive
feedback_idin the payload so you can act on the current post.
Sleekplan renders your response using a simple, documented component schema (see below for supported component types).
# Requests your endpoint will receive
Your endpoint must accept POST application/json and return application/json.
Two situations trigger calls to your endpoint:
- Initial render (view load)
- Payload contains your
product_id, thetype(view) and the current query such asfeedback_id. - Example payload:
{
"product_id": 12345,
"type": "admin_post",
"feedback_id": 6789
}
- Submit/interaction
- After the admin interacts, Sleekplan posts the submitted fields for your Canvas section along with any query params. A boolean
submit: trueis included. - Example payload:
{
"product_id": 12345,
"feedback_id": 6789,
"submit": true,
"button-action-create": true,
"create-item-title": "Improve SSO",
"create-item-description": "Support IdP-initiated SAML",
"create-item-project": "proj_abc",
"create-item-type": "Task"
}
Notes:
- Field names come from the
idyou assign to components (e.g., inputs, dropdowns, buttons, lists). For a button, Sleekplan posts{ [button.id]: true }when clicked. - For a list selection, Sleekplan posts
{ [list.id]: <selectedItem.id> }.
# Response schema (components you can return)
Return either a single object or an array of component objects. Sleekplan will normalize a single object to an array behind the scenes.
Supported component types and properties (as rendered by Sleekplan):
- text
- props:
text(string),style("header" | "paragraph")
- props:
- divider
- props: none
- button
- props:
id(string),label(string),style(string, e.g., "blue"),action({ type: "submit" | "link", href?: string })
- props:
- input
- props:
id,label(used as placeholder),title?(label above),submit?(boolean; if true, Enter/arrow submits)
- props:
- textarea
- props:
id,label,title?,submit?
- props:
- dropdown
- props:
id,title,options(array of{ id, text })
- props:
- link
- props:
title?,text,href
- props:
- list
- props:
id,title,items(array of items where each item is either{ type: "option", id, text }or{ type: "link", href, text })
- props:
Conditional visibility (optional): add a condition object to any component to show it only when another field matches a value.
{
"type": "dropdown",
"id": "create-item-type",
"title": "Item Type",
"condition": { "value": "create-item-project", "key": "proj_abc" },
"options": [ { "id": "Task", "text": "Task" } ]
}
Error display: to show an inline error for a section, you can return the first element as { "error": "Human readable message" }.
# End-to-end flow example
Initial render (show search/create options):
[
{ "type": "text", "text": "Search or create DevOps item", "style": "paragraph" },
{ "type": "input", "id": "input-action-search", "label": "Search...", "submit": true, "placeholder": "leave blank to skip" },
{ "type": "button", "id": "button-action-create", "label": "Create new item", "style": "blue", "action": { "type": "submit" } }
]
User types a query and submits; you respond with a list:
[
{
"type": "list",
"id": "link-item-list",
"title": "Items",
"items": [
{ "type": "option", "id": { "item_id": 42, "project_id": "p1" }, "text": "Item #42 - Task" },
{ "type": "option", "id": { "item_id": 43, "project_id": "p2" }, "text": "Item #43 - Bug" }
]
},
{ "type": "button", "id": "button-action-create", "label": "Create new item", "style": "blue", "action": { "type": "submit" } }
]
User selects an option; you link it and return a confirmation with an unlink button and a link:
[
{ "type": "link", "title": "Linked with:", "text": "DevOps #42", "href": "https://devops.example.com/items/42" },
{ "type": "button", "id": "button-action-unlink", "label": "Unlink item", "style": "blue", "action": { "type": "submit" } }
]
This mirrors how native integrations implement Canvas: the endpoint returns arrays of the component types above in response to stateful interactions.
# Security and verification
- Header: every request to your endpoint includes
X-Secret: <token>. The token is set when you install the Custom integration in Sleekplan. Reject requests that don’t match. - IP allow-list (optional): outbound calls originate from Sleekplan through a fixed proxy. You may allow-list
168.119.102.99if you restrict by IP. - Content types: requests use
Content-Type: application/jsonand expectAccept: application/json.
# Installing and configuring in Sleekplan
- In Sleekplan Admin, open Settings → Integrations → Custom, and click Install/Connect.
- Fill out the fields below, then Save.
Fields and requirements:
- Canvas URL (required if no Webhook URL)
- Your HTTPS endpoint that accepts and returns JSON per this guide.
- We call it on view load and on user actions with
Content-Type: application/json. - Must be publicly reachable over HTTPS. We include
X-Secretfor verification.
- Canvas section
- Currently only
admin_postis supported (Canvas shows on the feedback detail view’s sidebar).
- Currently only
- Webhook URL (optional)
- Your HTTPS endpoint to receive event webhooks you opt into.
- Receives JSON POSTs and includes the same
X-Secretheader.
- Webhook events (optional)
- Comma-separated list of events you’d like to receive (e.g.,
item.create, item.update).
- Comma-separated list of events you’d like to receive (e.g.,
- Secret token (required)
- Any non-empty string you choose. We’ll send it as
X-Secret: <token>on both Canvas and Webhook calls.
- Any non-empty string you choose. We’ll send it as
Validation and behavior:
- At least one of Canvas URL or Webhook URL must be valid to complete setup.
- URLs should be full HTTPS URLs (scheme + host). If a scheme is omitted, we’ll treat it as HTTPS.
- If your network restricts inbound calls by IP, you may allow‑list
168.119.102.99.
Example configuration:
- Canvas URL:
https://integrations.example.com/sleekplan/canvas - Canvas section:
admin_post - Webhook URL:
https://integrations.example.com/sleekplan/webhook - Webhook events:
item.create, item.update - Secret token:
prod-abc-xyz
After saving:
- Open any feedback post in Sleekplan Admin. The Canvas will request components from your Canvas URL and render them.
- Interactions (button clicks, list selections, submitted inputs) will post back to your Canvas URL with the submitted fields.
Under the hood, Sleekplan registers your Canvas and calls your URL for the configured view. Your endpoint’s JSON response is rendered directly in the admin UI.
# Minimal endpoint examples
Node.js/Express
import express from 'express';
const app = express();
app.use(express.json());
const SECRET = process.env.SLEEKPLAN_CANVAS_SECRET;
app.post('/sleekplan/canvas', (req, res) => {
if (req.get('X-Secret') !== SECRET) return res.status(401).json({ error: 'Unauthorized' });
const { type, feedback_id, submit, ['button-action-create']: createClicked } = req.body;
if (!submit) {
return res.json([
{ type: 'text', text: 'Search or create item', style: 'paragraph' },
{ type: 'input', id: 'input-action-search', label: 'Search...', submit: true },
{ type: 'button', id: 'button-action-create', label: 'Create new item', style: 'blue', action: { type: 'submit' } }
]);
}
if (createClicked) {
// Create item in your system here, then show confirmation
return res.json([
{ type: 'link', title: 'Linked with:', text: 'Item #123', href: 'https://example.com/items/123' },
{ type: 'button', id: 'button-action-unlink', label: 'Unlink item', style: 'blue', action: { type: 'submit' } }
]);
}
// Default fallback
return res.json([{ type: 'text', text: 'No action', style: 'paragraph' }]);
});
app.listen(3000, () => console.log('Canvas listening on :3000'));
PHP (plain)
<?php
$secret = getenv('SLEEKPLAN_CANVAS_SECRET');
$input = json_decode(file_get_contents('php://input'), true);
if (($_SERVER['HTTP_X_SECRET'] ?? '') !== $secret) {
http_response_code(401);
echo json_encode(['error' => 'Unauthorized']);
exit;
}
$submit = $input['submit'] ?? false;
if (! $submit) {
echo json_encode([
[ 'type' => 'text', 'text' => 'Hello Canvas', 'style' => 'header' ]
]);
exit;
}
echo json_encode([
[ 'type' => 'text', 'text' => 'Submitted', 'style' => 'paragraph' ]
]);
# Tips and gotchas
- Always return JSON arrays of components; a single object is accepted, but arrays are clearer.
- Use stable
idvalues for inputs/buttons; those keys appear in subsequent payloads. - To show a validation error, return
[ { "error": "Something went wrong" } ]as the first element. - Keep responses small and fast. The UI updates immediately with your returned components.
- If you need to link back to the Sleekplan post, you’ll receive
feedback_idand can build a URL likehttps://app.sleekplan.com/feedback/{feedback_id}.
If you need anything else for your Canvas integration, contact support and share your endpoint URL and a sample payload so we can help verify it end‑to‑end.