How to Build a DonutWork App
A programming-agnostic guide to architecting, registering, and deploying third-party integrations within the DonutWork ecosystem.
Building for DonutWork
This guide provides a conceptual and technical blueprint for developing third-party applications. The DonutWork framework is explicitly programming language agnostic, relying on standard HTTP/REST protocols and secure UI embedding to facilitate cross-system orchestration.
1. Application Registration
Before you begin development, you must register your application to obtain the necessary credentials for identity and platform access.
- Client ID: Your application's public identifier.
- Client Secret: A secure credential used to authorize your app to act on behalf of a company.
- Redirect URI: The URL where DonutWork will send authorization codes.
Refer to the Detailed OAuth Guide for the technical authentication handshake.
2. Capability Discovery (The Manifest)
DonutWork uses a Discovery Pattern to understand what your application is capable of. Instead of DonutWork calling your app blindly, your app must "declare" its capabilities (Actions).
Declaring Capabilities
Your application should send a PUT request to the /integrations.json endpoint. The manifest declares proxied capabilities for workflows and API calls, and optional UI actions that launch your app from the backoffice.
integrations:writePath, query, and header parameters
No path, query, or additional header parameters.
Request Body
{
"app_token": "YOUR_APP_SHARED_TOKEN",
"capabilities": [
{
"name": "ping",
"description": "Test connection health",
"endpoint": "https://your-app.com/api/ping.json",
"method": "GET",
"scopes": [
"workflow",
"api"
],
"inputs": []
},
{
"name": "provision_resource",
"description": "Create a new resource",
"endpoint": "https://your-app.com/api/provision.json",
"method": "POST",
"inputs": [
{
"name": "resource_name",
"type": "string",
"required": true
}
]
}
],
"ui_actions": [
{
"key": "print_customer",
"label": "Print customer data",
"placements": [
"customer:details"
]
}
]
}Request examples
curl --request PUT 'https://api.hub.donutwork.com/2026-02-01/integrations.json' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"app_token": "YOUR_APP_SHARED_TOKEN",
"capabilities": [
{
"name": "ping",
"description": "Test connection health",
"endpoint": "https://your-app.com/api/ping.json",
"method": "GET",
"scopes": [
"workflow",
"api"
],
"inputs": []
},
{
"name": "provision_resource",
"description": "Create a new resource",
"endpoint": "https://your-app.com/api/provision.json",
"method": "POST",
"inputs": [
{
"name": "resource_name",
"type": "string",
"required": true
}
]
}
],
"ui_actions": [
{
"key": "print_customer",
"label": "Print customer data",
"placements": [
"customer:details"
]
}
]
}'const response = await fetch('https://api.hub.donutwork.com/2026-02-01/integrations.json', {
method: 'PUT',
headers: {
Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"app_token": "YOUR_APP_SHARED_TOKEN",
"capabilities": [
{
"name": "ping",
"description": "Test connection health",
"endpoint": "https://your-app.com/api/ping.json",
"method": "GET",
"scopes": [
"workflow",
"api"
],
"inputs": []
},
{
"name": "provision_resource",
"description": "Create a new resource",
"endpoint": "https://your-app.com/api/provision.json",
"method": "POST",
"inputs": [
{
"name": "resource_name",
"type": "string",
"required": true
}
]
}
],
"ui_actions": [
{
"key": "print_customer",
"label": "Print customer data",
"placements": [
"customer:details"
]
}
]
}),
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
const error = await response.text();
throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
: contentType.includes('application/json') ? await response.json()
: await response.text();$request = curl_init('https://api.hub.donutwork.com/2026-02-01/integrations.json');
curl_setopt_array($request, [
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => '{
"app_token": "YOUR_APP_SHARED_TOKEN",
"capabilities": [
{
"name": "ping",
"description": "Test connection health",
"endpoint": "https://your-app.com/api/ping.json",
"method": "GET",
"scopes": [
"workflow",
"api"
],
"inputs": []
},
{
"name": "provision_resource",
"description": "Create a new resource",
"endpoint": "https://your-app.com/api/provision.json",
"method": "POST",
"inputs": [
{
"name": "resource_name",
"type": "string",
"required": true
}
]
}
],
"ui_actions": [
{
"key": "print_customer",
"label": "Print customer data",
"placements": [
"customer:details"
]
}
]
}',
]);
$response = curl_exec($request);
if ($response === false) {
$error = curl_error($request);
curl_close($request);
throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
: (str_contains($contentType, 'application/json')
? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.
- app_token: DonutWork will include this Bearer token in every
Authorizationheader when it calls your application. This allows your app to verify that the request is coming from DonutWork. - capabilities: An array of allowed actions, each with a defined
endpoint,method, and expectedinputs. - capabilities[].scopes: Optional
"workflow","api", or an array with both. Without it, the existing unrestricted behavior applies. With it, only the specified channels can call the capability. - ui_actions: An optional array of backoffice actions. Each action has a unique
key, a visiblelabel, and aplacementsarray. The only supported placement iscustomer:details. Omit the field to keep existing UI actions, or send[]to clear them. See the API reference for limits and validation rules.
Each PUT replaces capabilities, events, and app_token; include their current values when updating only ui_actions.
Actions on Customer Details
For an installed app with an HTTPS canvas_uri, each customer:details action appears in the customer page's More actions menu. Selecting it opens your app in its iframe with action_type set to the declared key and action_id set to the customer ID. DonutWork signs both parameters with the other launch parameters. Your app must verify the iframe signature before using them. The customer ID is context, not permission to access that customer's data; use your authorized API credentials for any data request.
3. Communication Pattern (The Action Logic)
When a user or a generic workflow triggers an action, DonutWork acts as a Secure Proxy.
- Trigger: DonutWork identifies the target
Action(e.g.,provision_resource). - Identification: DonutWork retrieves the
endpointandapp_tokenfrom your saved manifest. - Execution: DonutWork sends an HTTP request to your endpoint, including the
app_tokenin the header and the required data in the body. - Response: Your application processes the request and returns a standard JSON response.
4. UI Framework (The Iframe)
DonutWork embeds your application's frontend inside its own dashboard via an Iframe. To ensure a smooth user experience and maintain security, you must adhere to the following policies:
Cookie Security (SameSite=None)
Because your application is running in an Iframe context (cross-site), traditional session cookies may be blocked by modern browsers. You must configure your session cookies with:
SameSite=NoneSecure=true
Security Headers
DonutWork allows Iframe embedding from registered applications. On your end, you should ensure your headers allow the platform to display your content. For a deep dive into signature verification, see Iframe Context & Security.
Content-Security-Policy: frame-ancestors 'self' https://hub.donutwork.com;
X-Frame-Options: ALLOW-FROM https://hub.donutwork.com5. Event Synchronization (Webhooks)
To react to platform events in real-time (e.g., when a user pays a bill), you should register a Webhook.
- Endpoint: Your application should provide a dedicated HTTPS URL to receive POST payloads.
- Key Rotation: During registration, you will receive a
token. Use this key to verify theX-SIGNATUREheader on every incoming request.
6. Uninstall Callback (Company Data Cleanup)
When a company uninstalls your app from DonutWork, the platform sends a server-to-server callback to your configured uninstall_url.
- Method:
DELETE - Content-Type:
application/x-www-form-urlencoded - Body:
company_id: Company identifier to delete.verify: HMAC signature computed asHMAC_SHA256(company_id, client_secret).
Example payload:
DELETE /uninstall HTTP/1.1
Content-Type: application/x-www-form-urlencoded
company_id=699ecb44790ac4eea20e9cd3&verify=7c72b2...Verification rule on your side:
$expected = hash_hmac('sha256', $companyId, $clientSecret);
$isValid = hash_equals($expected, $verify);Implementation requirements:
- Validate
company_idandverifybefore deleting anything. - Delete only tenant-scoped data for that
company_id. - Make the endpoint idempotent (repeated calls must not fail).
- Return a
2xxresponse only when cleanup is completed (or already completed).
If your endpoint returns non-2xx, DonutWork will treat the uninstall callback as failed and local uninstall will be blocked.
Summary Checklist
- Register App and obtain Client ID/Secret.
- Implement OAuth 2.0 to obtain Platform Access Tokens.
- Use
PUT /integrations.jsonto declare your app's endpoints. - Implement your API endpoints to handle requests from the DonutWork proxy.
- Configure
SameSite=NoneandSecurecookies for your Iframe UI. - Register Webhooks for real-time event processing.
- Implement
DELETE uninstall_urlwithcompany_id+ HMACverifyand tenant-safe cleanup.
Donutwork Claude Code Skill
Integrate Donutwork faster with the official Claude Code skill package, including SKILL.md context, endpoint maps, entity models, and production-ready playbooks.
Authentication & Iframe Governance
Secure your application with OAuth 2.0 and manage seamless Iframe embedding within the DonutWork dashboard.