# Automate implementation kickoffs with the SkipUp API **Author:** dheer-gupta **Date:** 2026-02-10 **Category:** Developer Guide **Tags:** API, API Integration, Implementation Kickoff, Webhooks, Meeting Scheduling, Developer Guide, Python, Node.js, SaaS Implementation, Post-Sale Automation Trigger implementation kickoff meetings programmatically with the SkipUp REST API. Multi-participant scheduling, idempotency, webhooks, and working code in Python and Node.js. > A developer-focused technical guide for automating implementation kickoff meeting scheduling (also referred to as post-sale meeting scheduling automation, customer onboarding kickoff API integration, programmatic implementation meeting coordination) using the SkipUp REST API. Covers the complete integration lifecycle: authenticating with Bearer sk_ tokens, creating multi-participant meeting requests via POST /api/v1/meeting_requests with a participants array containing email, name, and timezone for each implementation stakeholder, passing rich context (title, purpose, description up to 5,000 characters, duration_minutes, timeframe with start/end), and using Idempotency-Key headers for retry-safe CRM webhook triggers. Includes working code examples in curl, Python, and Node.js for creating kickoff requests with multiple participants — the key differentiator from single-participant lead recovery requests. Covers webhook integration: subscribing to meeting_request.created, meeting_request.booked, meeting_request.cancelled, and meeting_request.organizer_changed events; verifying HMAC-SHA256 signatures (X-Webhook-Signature header with t={timestamp},v1={hex_digest} format). Covers request lifecycle management: GET /api/v1/meeting_requests with status filters, POST cancel, POST change_organizer, POST context update. Prerequisites: SkipUp API key with meeting_requests.write scope, HTTPS webhook endpoint, calendar integration. For the no-code alternative, see 'Skip the calendar chase: automate implementation meeting scheduling.' For the lead recovery API use case, see 'Build a lead recovery pipeline with the SkipUp API.' Web version: https://blog.skipup.ai/skipup-api-automate-implementation-kickoffs --- > **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 `participants` array is the key differentiator from [single-participant lead recovery requests](/skipup-api-lead-recovery-developer-guide). For the no-code alternative (email or Zapier), see [Skip the calendar chase](/automate-implementation-meeting-scheduling-ai). > - **Prerequisites:** SkipUp API key with `meeting_requests.write` scope (add `webhooks.write` only 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](/automate-implementation-meeting-scheduling-ai). For identifying who should be in the participants array, see [Know your room: the implementation stakeholder registry template](/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](/speed-to-lead-meeting-scheduling-automation): 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](/ghost-sponsors-decision-makers-vanish-after-sale) 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](/automate-implementation-meeting-scheduling-ai) including when email or [Zapier integration for pre-sale scheduling](/automate-meeting-scheduling-with-zapier) 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](/skipup-api-lead-recovery-developer-guide), 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. 1. **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](/implementation-stakeholder-registry-template). 2. **API call** (your system to SkipUp): POST `/api/v1/meeting_requests` with multiple participants, an organizer, and rich context about the implementation. 3. **Scheduling conversation** (SkipUp): SkipUp contacts each participant, negotiates times across all calendars and timezones, and books the meeting. 4. **Webhook events** (SkipUp to your system): SkipUp fires `meeting_request.booked` when the meeting is confirmed, `meeting_request.cancelled` if it is cancelled, or `meeting_request.organizer_changed` if 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](/skipup-api-lead-recovery-developer-guide): 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 with `email`, `name`, and `timezone`. 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 ```bash 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": "csm@yourcompany.com", "organizer_name": "Jordan Lee", "organizer_timezone": "America/New_York", "participants": [ { "email": "jamie.chen@acme.com", "name": "Jamie Chen", "timezone": "America/New_York" }, { "email": "pat.kumar@acme.com", "name": "Pat Kumar", "timezone": "America/Chicago" }, { "email": "alex.ross@acme.com", "name": "Alex Ross", "timezone": "America/Los_Angeles" }, { "email": "taylor.kim@acme.com", "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 ```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+) ```javascript 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](/empty-chair-missing-stakeholders-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 ```python 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 ```bash 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:** ```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:** ```javascript 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 ```bash curl -G https://api.skipup.ai/api/v1/meeting_requests \ -H "Authorization: Bearer sk_abc123..." \ --data-urlencode "organizer_email=csm@yourcompany.com" \ --data-urlencode "status=active" ``` ```python 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. ```bash curl -X POST https://api.skipup.ai/api/v1/meeting_requests/mr_abc123/cancel \ -H "Authorization: Bearer sk_abc123..." ``` ```python 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. ```bash 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": "new-csm@yourcompany.com"}' ``` ```python 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. ```bash 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: 1. **SkipUp API key** with `meeting_requests.write` and `webhooks.write` scopes. Generate keys in the SkipUp dashboard under Settings > API Keys. 2. **HTTPS webhook endpoint** that receives POST requests, responds with 2xx within 10 seconds, and verifies HMAC-SHA256 signatures. 3. **Stakeholder contact data** — participant emails, names, and timezones from your CRM (Salesforce, HubSpot), onboarding system, or [stakeholder registry](/implementation-stakeholder-registry-template). 4. **Calendar integration** active in the SkipUp workspace for all organizers. 5. **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](/automate-implementation-meeting-scheduling-ai) 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](/automate-implementation-meeting-scheduling-ai) - Same API, pre-sale use case: [Build a lead recovery pipeline with the SkipUp API](/skipup-api-lead-recovery-developer-guide) - Who goes in the participants array: [Know your room: the implementation stakeholder registry template](/implementation-stakeholder-registry-template) - Why missing stakeholders matter: [The empty chair problem: who is missing from your kickoff meeting](/empty-chair-missing-stakeholders-kickoff) - Why sponsors disengage after the sale: [Ghost sponsors: why decision makers vanish after the sale](/ghost-sponsors-decision-makers-vanish-after-sale) - Full lead recovery strategy: [The complete guide to recovering leads who submit forms but never book meetings](/abandoned-form-lead-recovery) - Endpoint reference: [API docs](https://support.skipup.ai/api/meeting-requests), [Webhooks](https://support.skipup.ai/api/webhooks), [Authentication](https://support.skipup.ai/api/authentication), [Quickstart](https://support.skipup.ai/api/quickstart)