Skip to content

Webhooks API

The Webhooks API provides endpoints for creating issues and deployments programmatically. This is useful for integrating GuideMode with custom tools, CI/CD pipelines, or systems that don’t have native integrations.

The v1 webhook API uses GuideMode’s internal data model directly, so no label mapping is required. All data created via these webhooks is tagged with provider "manual" to distinguish it from GitHub, Jira, or other native integrations.

https://app.guidemode.dev/api/webhooks/v1

All requests require an API key in the Authorization header:

Terminal window
curl -H "Authorization: Bearer gai_your_api_key" \
-H "Content-Type: application/json" \
https://app.guidemode.dev/api/webhooks/v1/issues

You can obtain an API key by:

  1. Running guidemode login in the CLI
  2. Creating one in the GuideMode web UI under Settings > API Keys
POST /api/webhooks/v1/issues

Creates a new issue or updates an existing one (matched by externalId).

{
"projectKey": "owner/repo",
"externalId": "JIRA-123",
"title": "Fix login button",
"type": "bug",
"state": "open",
"body": "The login button doesn't work on Safari",
"url": "https://jira.example.com/browse/JIRA-123",
"authorUsername": "developer1",
"assigneeUsername": "developer2",
"labels": ["critical", "ui"],
"createdAt": "2024-01-15T10:00:00Z",
"closedAt": null,
"metadata": {
"priority": "high",
"sprint": "Sprint 23"
}
}
Field Type Required Description
projectKey string Yes Project identifier (e.g., "owner/repo")
externalId string Yes Unique ID in your source system
title string Yes Issue title
type string Yes One of: feature, bug, chore, discovery, incident, other
state string Yes One of: open, closed, in_progress
number number No Issue number (auto-generated if omitted)
body string No Issue description
url string No Link to source issue
authorUsername string No Creator username
assigneeUsername string No Primary assignee
labels string[] No Label names
createdAt string No ISO8601 timestamp (defaults to now)
closedAt string No ISO8601 timestamp when closed
metadata object No Custom key-value data
{
"success": true,
"action": "created",
"id": "550e8400-e29b-41d4-a716-446655440000",
"externalId": "JIRA-123"
}

The action field indicates whether the issue was created or updated.

POST /api/webhooks/v1/deployments

Creates a new deployment or updates an existing one (matched by externalId).

{
"projectKey": "owner/repo",
"externalId": "deploy-123",
"ref": "main",
"sha": "abc123def456789012345678901234567890abcd",
"environment": "production",
"status": "success",
"task": "deploy",
"description": "Deploying v1.2.3",
"url": "https://myapp.example.com",
"creatorUsername": "deployer",
"isProduction": true,
"isRollback": false,
"rollbackFromSha": null,
"createdAt": "2024-01-15T10:00:00Z",
"metadata": {
"version": "1.2.3",
"buildNumber": "456"
}
}
Field Type Required Description
projectKey string Yes Project identifier (e.g., "owner/repo")
externalId string Yes Unique deployment ID
ref string Yes Branch/tag being deployed
sha string Yes Commit SHA (7-40 characters)
environment string Yes One of: production, staging, development, qa, preview, other
status string Yes One of: pending, queued, in_progress, success, failure, error, inactive
task string No Task type (default: "deploy")
description string No Deployment description
url string No Deployment URL
creatorUsername string No Deployer username
isProduction boolean No Override production detection
isRollback boolean No Flag as rollback (sets task to "rollback")
rollbackFromSha string No SHA being rolled back from (7-40 characters)
createdAt string No ISO8601 timestamp (defaults to now)
metadata object No Custom key-value data
{
"success": true,
"action": "created",
"id": "550e8400-e29b-41d4-a716-446655440001",
"externalId": "deploy-123"
}
{
"error": "Validation failed",
"details": [
{ "path": "sha", "message": "sha must be 40 characters" },
{ "path": "type", "message": "Invalid enum value" }
]
}
{
"error": "Unauthorized"
}
{
"error": "Internal server error"
}

Create an issue:

Terminal window
curl -X POST https://app.guidemode.dev/api/webhooks/v1/issues \
-H "Authorization: Bearer gai_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"projectKey": "myorg/myrepo",
"externalId": "TICKET-123",
"title": "Implement dark mode",
"type": "feature",
"state": "open",
"labels": ["enhancement", "ui"]
}'

Record a deployment:

Terminal window
curl -X POST https://app.guidemode.dev/api/webhooks/v1/deployments \
-H "Authorization: Bearer gai_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"projectKey": "myorg/myrepo",
"externalId": "deploy-456",
"ref": "main",
"sha": "abc123def456789012345678901234567890abcd",
"environment": "production",
"status": "success"
}'
const API_KEY = process.env.GUIDEMODE_API_KEY;
const BASE_URL = 'https://app.guidemode.dev/api/webhooks/v1';
async function createIssue(issue: {
projectKey: string;
externalId: string;
title: string;
type: 'feature' | 'bug' | 'chore' | 'discovery' | 'incident' | 'other';
state: 'open' | 'closed' | 'in_progress';
body?: string;
labels?: string[];
}) {
const response = await fetch(`${BASE_URL}/issues`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(issue),
});
if (!response.ok) {
const error = await response.json();
throw new Error(`API error: ${error.error}`);
}
return response.json();
}
async function recordDeployment(deployment: {
projectKey: string;
externalId: string;
ref: string;
sha: string;
environment: 'production' | 'staging' | 'development' | 'qa' | 'preview' | 'other';
status: 'pending' | 'queued' | 'in_progress' | 'success' | 'failure' | 'error';
}) {
const response = await fetch(`${BASE_URL}/deployments`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(deployment),
});
if (!response.ok) {
const error = await response.json();
throw new Error(`API error: ${error.error}`);
}
return response.json();
}
import os
import requests
API_KEY = os.environ['GUIDEMODE_API_KEY']
BASE_URL = 'https://app.guidemode.dev/api/webhooks/v1'
def create_issue(project_key, external_id, title, issue_type, state, **kwargs):
response = requests.post(
f'{BASE_URL}/issues',
headers={
'Authorization': f'Bearer {API_KEY}',
'Content-Type': 'application/json',
},
json={
'projectKey': project_key,
'externalId': external_id,
'title': title,
'type': issue_type,
'state': state,
**kwargs,
}
)
response.raise_for_status()
return response.json()
def record_deployment(project_key, external_id, ref, sha, environment, status, **kwargs):
response = requests.post(
f'{BASE_URL}/deployments',
headers={
'Authorization': f'Bearer {API_KEY}',
'Content-Type': 'application/json',
},
json={
'projectKey': project_key,
'externalId': external_id,
'ref': ref,
'sha': sha,
'environment': environment,
'status': status,
**kwargs,
}
)
response.raise_for_status()
return response.json()

When you create an issue or deployment, if the specified projectKey doesn’t exist, GuideMode will automatically create a new project with:

  • provider: "manual"
  • sourceType: "manual"
  • syncIssues: true
  • syncPullRequests: true
  • syncDeployments: true

This means you can start tracking work items immediately without pre-configuring projects.

Both endpoints use upsert logic based on externalId:

  1. If an issue/deployment with the same tenantId + provider + externalId exists, it’s updated
  2. If no match is found, a new record is created

This makes it safe to call the API multiple times with the same externalId - you’ll always get the latest state synced.

  • 100 requests per minute per API key
  • 1000 requests per hour per API key

If you exceed these limits, you’ll receive a 429 Too Many Requests response.

  1. Use consistent external IDs: Use your source system’s unique identifiers as externalId to enable proper upsert behavior.

  2. Include timestamps: Provide createdAt and closedAt for accurate metrics. Without these, GuideMode uses the current time.

  3. Set appropriate types: Use the correct type for issues to enable proper categorization in analytics dashboards.

  4. Track deployment status changes: Call the deployment endpoint multiple times as status changes (pending → in_progress → success/failure).

  5. Use metadata for custom fields: Store additional data in the metadata field for reference, but note that it’s not indexed for queries.