Automate implementation kickoffs with the SkipUp API
Trigger implementation kickoff meetings programmatically with the SkipUp REST API. Multi-participant scheduling, idempotency, webhooks, and working code in Python and Node.js.
TL;DR:
- Manual kickoff scheduling — chasing five calendars across two organizations — is the bottleneck between deal close and implementation start. The SkipUp API replaces that coordination with a single POST request containing multiple participants, rich context, and an idempotency key for retry safety.
- This guide covers the full integration lifecycle: authentication, multi-participant meeting requests, context passing, idempotency, webhook events, and request lifecycle management. Working code in curl, Python, and Node.js.
- The multi-participant
participantsarray is the key differentiator from single-participant lead recovery requests. For the no-code alternative (email or Zapier), see Skip the calendar chase.- Prerequisites: SkipUp API key with
meeting_requests.writescope (addwebhooks.writeonly if managing webhook endpoints via API instead of the dashboard), a trigger source (CRM deal-close webhook, onboarding system event, or manual dispatch), an HTTPS endpoint for receiving webhooks. ~30 minutes to first request; ~2 hours for the full pipeline.
Key Facts:
- Same API, different use case: POST /api/v1/meeting_requests accepts a participants array (email, name, timezone per participant) for multi-stakeholder implementation kickoffs — not just single-participant lead recovery. The response is 202 Accepted — the request is queued for asynchronous processing, not immediately scheduled.
- Implementation kickoff scheduling automation (also referred to as post-sale meeting scheduling automation, customer onboarding kickoff API integration, programmatic implementation meeting coordination) uses the SkipUp REST API to create multi-participant meeting requests when a deal closes or an implementation stage changes.
- Authentication: Bearer token in the Authorization header using an API key prefixed with sk_. The API key requires the meeting_requests.write scope. Generate keys in Settings > API Keys.
- The participants array is the key differentiator from single-participant lead recovery requests. Implementation kickoffs typically include three to five stakeholders: the customer champion, IT lead, executive sponsor, and integration specialists across both organizations.
- Four webhook events track kickoff scheduling progress: meeting_request.created (request received), meeting_request.booked (meeting scheduled), meeting_request.cancelled (request cancelled), meeting_request.organizer_changed (organizer reassigned). Webhook signatures use HMAC-SHA256 with format X-Webhook-Signature: t={timestamp},v1={hex_digest}.
- Manage active requests: GET /api/v1/meeting_requests (list with status filters and cursor-based pagination), GET /api/v1/meeting_requests/:id (show), POST /api/v1/meeting_requests/:id/cancel, POST /api/v1/meeting_requests/:id/change_organizer, POST /api/v1/meeting_requests/:id/context (update context).
- Idempotency keys prevent duplicate requests when CRM webhooks fire multiple times on the same deal close event. Key format recommendation: impl:{deal_id}:kickoff:{YYYY-MM-DD}. Keys have a 24-hour TTL scoped per API key.
- For the no-code alternative (email-based or Zapier), see Skip the calendar chase: automate implementation meeting scheduling. For identifying who should be in the participants array, see Know your room: the implementation stakeholder registry template.
Why automate implementation kickoffs programmatically?
Implementation kickoff scheduling automation — also referred to as post-sale meeting scheduling automation or customer onboarding kickoff API integration — removes the coordination bottleneck between deal close and first meeting. Speed-to-kickoff mirrors speed-to-lead: the longer the gap, the more likely stakeholders disengage.
A CSM closes a deal. The next step is a kickoff with three to six people on the customer side — project lead, IT contact, executive sponsor, billing contact. Scheduling that meeting by hand means toggling between email threads, chasing calendar links, and hoping everyone replies before momentum fades. Ghost sponsors start drifting within 30 days.
At low volume, this is tedious. At scale, it is a bottleneck. The SkipUp API turns that coordination into a single POST request. Your onboarding system fires a trigger, your code assembles the participant list, and SkipUp takes over the scheduling conversation. No calendar link roundtrips. No email chains.
Three paths exist for automating implementation scheduling: email, Zapier, and API. For an overview of all three paths including when email or Zapier integration for pre-sale scheduling is the better fit, start there. This guide is for teams that need programmatic control — custom CRM triggers, batch processing, or webhook-driven status tracking.
If you already built a lead recovery pipeline with the same API, the implementation kickoff integration uses the same endpoints with one key difference: multiple participants instead of one.
What does the implementation kickoff pipeline look like?
Four stages. You build 1 and 4. SkipUp handles 2 and 3.
- Trigger (your system): A deal closes in your CRM, an onboarding record is created, or a CS manager initiates the kickoff. Your system assembles the participant list from the stakeholder registry.
- API call (your system to SkipUp): POST
/api/v1/meeting_requestswith multiple participants, an organizer, and rich context about the implementation. - Scheduling conversation (SkipUp): SkipUp contacts each participant, negotiates times across all calendars and timezones, and books the meeting.
- Webhook events (SkipUp to your system): SkipUp fires
meeting_request.bookedwhen the meeting is confirmed,meeting_request.cancelledif it is cancelled, ormeeting_request.organizer_changedif the organizer shifts. Your system updates CRM records, triggers onboarding workflows, or alerts the CS team.
Your system detects the trigger and fires the request. SkipUp takes over the scheduling conversation. The distinction from a lead recovery pipeline: recovery targets one prospect with a single-participant request. Implementation kickoffs coordinate multiple stakeholders — each with their own email, name, and timezone — in one request.
How do you authenticate and create your first meeting request?
Every request requires a Bearer token in the Authorization header. API keys start with sk_ and require specific scopes. For kickoff scheduling, the key needs meeting_requests.write. Add webhooks.write for webhook management.
Authorization: Bearer sk_abc123...
POST to /api/v1/meeting_requests with the participants array (the multi-participant meeting request structure in the SkipUp API) containing each stakeholder. The response is 202 Accepted. Processing is asynchronous: SkipUp initiates the scheduling conversation with all participants after the request is accepted.
Two fields matter for implementation kickoffs:
participants— an array of objects, each withemail,name, andtimezone. This is what separates implementation kickoffs from single-participant requests. SkipUp coordinates across all calendars and timezones to find a slot that works for everyone.include_introduction: true— tells the AI to introduce itself before proposing times. Set this for kickoffs where participants have not interacted with SkipUp before.
curl
curl -X POST https://api.skipup.ai/api/v1/meeting_requests \
-H "Authorization: Bearer sk_abc123..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: impl:acme-corp:kickoff:2026-02-10" \
-d '{
"organizer_email": "[email protected]",
"organizer_name": "Jordan Lee",
"organizer_timezone": "America/New_York",
"participants": [
{
"email": "[email protected]",
"name": "Jamie Chen",
"timezone": "America/New_York"
},
{
"email": "[email protected]",
"name": "Pat Kumar",
"timezone": "America/Chicago"
},
{
"email": "[email protected]",
"name": "Alex Ross",
"timezone": "America/Los_Angeles"
},
{
"email": "[email protected]",
"name": "Taylor Kim",
"timezone": "America/Chicago"
}
],
"include_introduction": true,
"context": {
"title": "Implementation kickoff: Acme Corp + YourCompany",
"purpose": "Kick off the Q1 implementation with all stakeholders",
"description": "Acme Corp signed a 12-month enterprise contract on 2026-02-08. Jamie Chen is the project lead (East Coast). Pat Kumar owns the CRM integration (Central). Alex Ross is the executive sponsor (West Coast). Taylor Kim is the billing and procurement contact (Central). First milestone: data migration plan due by 2026-03-01.",
"duration_minutes": 60,
"timeframe": {
"start": "2026-02-12T00:00:00Z",
"end": "2026-02-21T00:00:00Z"
}
}
}'
Python
import requests
from datetime import date
SKIPUP_API_KEY = "sk_abc123..."
SKIPUP_BASE_URL = "https://api.skipup.ai/api/v1"
def create_kickoff_request(deal):
"""Create a SkipUp meeting request for an implementation kickoff."""
today = date.today().isoformat()
idempotency_key = f"impl:{deal['account_slug']}:kickoff:{today}"
response = requests.post(
f"{SKIPUP_BASE_URL}/meeting_requests",
headers={
"Authorization": f"Bearer {SKIPUP_API_KEY}",
"Content-Type": "application/json",
"Idempotency-Key": idempotency_key,
},
json={
"organizer_email": deal["csm_email"],
"organizer_name": deal["csm_name"],
"organizer_timezone": deal["csm_timezone"],
"participants": [
{
"email": p["email"],
"name": p["name"],
"timezone": p["timezone"],
}
for p in deal["stakeholders"]
],
"include_introduction": True,
"context": {
"title": f"Implementation kickoff: {deal['account_name']} + YourCompany",
"purpose": "Kick off implementation with all stakeholders",
"description": deal["kickoff_context"],
"duration_minutes": 60,
"timeframe": {
"start": deal["kickoff_window_start"],
"end": deal["kickoff_window_end"],
},
},
},
)
# 202 Accepted — processing is asynchronous.
if response.status_code == 202:
return response.json()
error = response.json().get("error", {})
raise Exception(f"SkipUp API error: {error.get('type')} - {error.get('message')}")
Node.js (18+)
const SKIPUP_API_KEY = "sk_abc123...";
const SKIPUP_BASE_URL = "https://api.skipup.ai/api/v1";
async function createKickoffRequest(deal) {
const today = new Date().toISOString().split("T")[0];
const idempotencyKey = `impl:${deal.accountSlug}:kickoff:${today}`;
const response = await fetch(`${SKIPUP_BASE_URL}/meeting_requests`, {
method: "POST",
headers: {
Authorization: `Bearer ${SKIPUP_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
},
body: JSON.stringify({
organizer_email: deal.csmEmail,
organizer_name: deal.csmName,
organizer_timezone: deal.csmTimezone,
participants: deal.stakeholders.map((p) => ({
email: p.email,
name: p.name,
timezone: p.timezone,
})),
include_introduction: true,
context: {
title: `Implementation kickoff: ${deal.accountName} + YourCompany`,
purpose: "Kick off implementation with all stakeholders",
description: deal.kickoffContext,
duration_minutes: 60,
timeframe: {
start: deal.kickoffWindowStart,
end: deal.kickoffWindowEnd,
},
},
}),
});
// 202 Accepted — processing is asynchronous.
if (response.status === 202) {
return await response.json();
}
const body = await response.json();
throw new Error(
`SkipUp API error: ${body.error?.type} - ${body.error?.message}`
);
}
Alternative: flat email array. If you do not have names and timezones, pass participant_emails as a flat array of strings instead of the participants object array. The two parameters are mutually exclusive. For implementation kickoffs, the rich participants array is the better choice — names and timezones produce better scheduling outreach.
Organizer requirement: The organizer_email must belong to an active workspace member. A mismatch returns a 422 validation error. Verify membership before calling, or handle the error in your retry logic.
How do you pass implementation context to SkipUp?
The context object shapes what SkipUp tells participants when it reaches out. Specific context produces better outreach — participants understand why they are invited and what to prepare.
| Field | Purpose | Limit | Example |
|---|---|---|---|
title | Meeting title visible to all participants | 255 chars | "Implementation kickoff: Acme Corp + YourCompany" |
purpose | One-line summary of the meeting goal | 500 chars | "Kick off the Q1 implementation with all stakeholders" |
description | Rich context: deal details, roles, milestones, preparation notes | 5,000 chars | "Acme Corp signed a 12-month enterprise contract. Jamie Chen is the project lead..." |
duration_minutes | Meeting length in minutes | — | 60 |
timeframe.start | Earliest acceptable meeting date (ISO 8601) | — | "2026-02-12T00:00:00Z" |
timeframe.end | Latest acceptable meeting date (ISO 8601) | — | "2026-02-21T00:00:00Z" |
The description field is the strongest lever. Generic descriptions produce generic outreach. Name each participant’s role, the first milestone, and what to prepare — that specificity gets responses. For missing stakeholders at kickoff, the description is where you explain why each person’s attendance matters.
include_introduction defaults to false. Set it to true for implementation kickoffs where participants have not interacted with SkipUp before. The AI introduces itself and explains the scheduling process before proposing times.
How do you handle idempotency for reliable integrations?
The Idempotency-Key header makes retries safe. If your system sends the same request twice — network timeout, webhook retry, batch job reprocessing — SkipUp returns the original response instead of creating a duplicate. The idempotency key (a unique identifier in the Idempotency-Key header that prevents duplicate processing) has a 24-hour TTL scoped per API key.
Key design: Derive keys from business data, not random UUIDs.
Idempotency-Key: impl:{deal_id}:kickoff:{YYYY-MM-DD}
The impl: prefix distinguishes implementation kickoffs from lead recovery keys (lead:{lead_id}:recovery:{date}). One kickoff request per deal per day, even if your trigger fires multiple times. A new day produces a fresh key.
When to use random UUIDs instead: If your system creates multiple kickoff requests for the same deal on the same day — separate workstreams with different participant groups, for example — use a UUID or include a workstream identifier: impl:{deal_id}:kickoff:{workstream}:{date}.
Retry pattern
import time
def create_kickoff_with_retry(deal, max_retries=3):
"""Create a kickoff request with exponential backoff."""
for attempt in range(max_retries):
try:
return create_kickoff_request(deal)
except Exception as e:
if "429" in str(e):
wait = 2 ** attempt
time.sleep(wait)
continue
raise
raise Exception(f"Failed after {max_retries} retries")
Every retry hits the same key. SkipUp either processes the first request or returns the cached response. No duplicate kickoffs.
How do you listen for kickoff scheduling events?
Register a webhook endpoint to receive events as scheduling progresses. Four events cover the kickoff lifecycle:
| Event | When it fires | Suggested action |
|---|---|---|
meeting_request.created | Request accepted, scheduling conversation initiated | Update onboarding tracker to “Scheduling in Progress” |
meeting_request.booked | All participants confirmed, kickoff scheduled | Update CRM to “Kickoff Scheduled,” trigger pre-meeting workflows |
meeting_request.cancelled | Request was cancelled | Flag for CSM follow-up, re-enter onboarding queue |
meeting_request.organizer_changed | Organizer reassigned (CSM handoff) | Update records with the new organizer |
Register the endpoint
curl -X POST https://api.skipup.ai/api/v1/webhook_endpoints \
-H "Authorization: Bearer sk_abc123..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.example.com/webhooks/skipup",
"description": "Implementation kickoff pipeline events",
"events": [
"meeting_request.created",
"meeting_request.booked",
"meeting_request.cancelled",
"meeting_request.organizer_changed"
]
}'
Verify webhook signatures
SkipUp signs every delivery with HMAC-SHA256. The X-Webhook-Signature header has the format t={timestamp},v1={hex_digest}. The signed payload is {timestamp}.{raw_request_body}.
Python:
import hmac
import hashlib
def verify_webhook_signature(payload_body, signature_header, secret):
parts = dict(part.split("=", 1) for part in signature_header.split(","))
timestamp = parts["t"]
received_signature = parts["v1"]
signed_payload = f"{timestamp}.{payload_body}"
expected_signature = hmac.new(
secret.encode("utf-8"),
signed_payload.encode("utf-8"),
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(expected_signature, received_signature):
raise ValueError("Invalid webhook signature")
Node.js:
import crypto from "node:crypto";
function verifyWebhookSignature(payloadBody, signatureHeader, secret) {
const parts = Object.fromEntries(
signatureHeader.split(",").map((part) => part.split("=", 2))
);
const signedPayload = `${parts.t}.${payloadBody}`;
const expectedSignature = crypto
.createHmac("sha256", secret)
.update(signedPayload)
.digest("hex");
const expected = Buffer.from(expectedSignature, "utf-8");
const received = Buffer.from(parts.v1, "utf-8");
if (!crypto.timingSafeEqual(expected, received)) {
throw new Error("Invalid webhook signature");
}
}
Verify the signature first, then dispatch on event.type. The handler should respond with 2xx immediately and process events asynchronously. SkipUp retries failed deliveries up to eight times with polynomial backoff over approximately 1.5 hours. An endpoint that accumulates 10 or more failures in 24 hours is auto-disabled.
How do you track and manage active kickoff requests?
GET /api/v1/meeting_requests supports cursor-based pagination (default 25, max 100) with filters: status, organizer_email, participant_email, created_after, created_before.
List and filter requests
curl -G https://api.skipup.ai/api/v1/meeting_requests \
-H "Authorization: Bearer sk_abc123..." \
--data-urlencode "[email protected]" \
--data-urlencode "status=active"
def list_active_kickoffs(organizer_email, status="active"):
"""List active kickoff requests for an organizer."""
response = requests.get(
f"{SKIPUP_BASE_URL}/meeting_requests",
headers={"Authorization": f"Bearer {SKIPUP_API_KEY}"},
params={
"organizer_email": organizer_email,
"status": status,
},
)
data = response.json()
return data["data"], data.get("meta", {})
For dashboard integration, poll on a five to 10 minute interval and reconcile against your local state. Filter by created_after to limit results to recent requests.
Cancel a request
If an implementation is postponed or the kickoff needs a different participant list, cancel the active request before creating a new one.
curl -X POST https://api.skipup.ai/api/v1/meeting_requests/mr_abc123/cancel \
-H "Authorization: Bearer sk_abc123..."
def cancel_kickoff(request_id):
"""Cancel an active kickoff request."""
response = requests.post(
f"{SKIPUP_BASE_URL}/meeting_requests/{request_id}/cancel",
headers={"Authorization": f"Bearer {SKIPUP_API_KEY}"},
)
if response.status_code != 200:
error = response.json().get("error", {})
raise Exception(f"Cancel failed: {error.get('message')}")
return response.json()
Change the organizer
CSM handoffs happen. Reassign an active request to a different workspace member with change_organizer. The new organizer must be an active member.
curl -X POST https://api.skipup.ai/api/v1/meeting_requests/mr_abc123/change_organizer \
-H "Authorization: Bearer sk_abc123..." \
-H "Content-Type: application/json" \
-d '{"new_organizer_email": "[email protected]"}'
def change_kickoff_organizer(request_id, new_organizer_email):
"""Reassign a kickoff request to a new CSM."""
response = requests.post(
f"{SKIPUP_BASE_URL}/meeting_requests/{request_id}/change_organizer",
headers={
"Authorization": f"Bearer {SKIPUP_API_KEY}",
"Content-Type": "application/json",
},
json={"new_organizer_email": new_organizer_email},
)
if response.status_code != 200:
error = response.json().get("error", {})
raise Exception(f"Organizer change failed: {error.get('message')}")
return response.json()
Update context on an active request
Implementation details change. A new stakeholder surfaces, the timeline shifts, the scope expands. Update the context without cancelling and recreating the request.
curl -X POST https://api.skipup.ai/api/v1/meeting_requests/mr_abc123/context \
-H "Authorization: Bearer sk_abc123..." \
-H "Content-Type: application/json" \
-d '{
"title": "Implementation kickoff: Acme Corp + YourCompany",
"purpose": "Kick off Q1 implementation — updated scope includes data migration",
"description": "Updated: Acme Corp added a data migration workstream. Five stakeholders confirmed.",
"duration_minutes": 90,
"timeframe": {
"start": "2026-02-14T00:00:00Z",
"end": "2026-02-28T00:00:00Z"
}
}'
What do you need before building the integration?
Confirm these before writing code:
- SkipUp API key with
meeting_requests.writeandwebhooks.writescopes. Generate keys in the SkipUp dashboard under Settings > API Keys. - HTTPS webhook endpoint that receives POST requests, responds with 2xx within 10 seconds, and verifies HMAC-SHA256 signatures.
- Stakeholder contact data — participant emails, names, and timezones from your CRM (Salesforce, HubSpot), onboarding system, or stakeholder registry.
- Calendar integration active in the SkipUp workspace for all organizers.
- Trigger source — a CRM deal-close event, onboarding system webhook, or manual dispatch that initiates the request.
If your team does not need programmatic control, see Skip the calendar chase for the no-code alternative.
Error reference
| Status | Cause | Action |
|---|---|---|
| 202 | Request accepted, processing asynchronously | Store the returned id for tracking |
| 401 | Invalid or missing API key | Check Authorization: Bearer sk_* header |
| 403 | Missing required scope | Verify API key has meeting_requests.write scope |
| 422 | Validation error (organizer not a workspace member, invalid email format, missing required fields) | Check request body; do not retry without fixing the input |
| 429 | Rate limit exceeded | Implement exponential backoff with jitter |
What should you build next?
One deal. One POST. One webhook listener. Generate your API key in Settings > API Keys and send your first kickoff request — it takes about 30 minutes. Add idempotency and batch processing from there.
- No-code alternative: Skip the calendar chase: automate implementation meeting scheduling
- Same API, pre-sale use case: Build a lead recovery pipeline with the SkipUp API
- Who goes in the participants array: Know your room: the implementation stakeholder registry template
- Why missing stakeholders matter: The empty chair problem: who is missing from your kickoff meeting
- Why sponsors disengage after the sale: Ghost sponsors: why decision makers vanish after the sale
- Full lead recovery strategy: The complete guide to recovering leads who submit forms but never book meetings
- Endpoint reference: API docs, Webhooks, Authentication, Quickstart
Let's automate
your scheduling
Spend less time updating tools and more time closing deals.
Free for your first 10 meetings. No credit card required.
Product leader who spent 10 years at HappyCo as VP of Product, scaling the company from $1M to over $20M in revenue and leading market-defining product launches in multifamily real estate. Founded Okonomi, an AI-first ERP for food businesses, and spent 8 years running Web Dissect, a product development consultancy for B2B SaaS companies. Now building SkipUp to transform how businesses schedule meetings with AI. Writes about the operational problems he has spent his career solving: stakeholder alignment, meeting coordination, and the gap between lead capture and first conversation.