Okta Workflows → ManageEngine ServiceDesk Plus
Complete card-by-card templates for automating ticket creation, assignment, and team management in ManageEngine SDP Cloud from Okta Workflows.
Setup Prerequisites
Cloud SDP (your setup): Authentication uses Zoho OAuth 2.0. Access tokens expire every 60 minutes, so Flow 0 handles automatic refresh. Complete these steps before building any workflow.
Step 1: Create Zoho OAuth Self Client
- Go to
https://api-console.zoho.com/ - Click Add Client → choose Self Client
- Generate an authorization code with scopes:
SDPOnDemand.requests.ALL,SDPOnDemand.setup.ALL - Exchange the auth code for tokens (code expires in ~10 minutes):
POST https://accounts.zoho.com/oauth/v2/token grant_type=authorization_code &client_id={{YOUR_CLIENT_ID}} &client_secret={{YOUR_CLIENT_SECRET}} &code={{AUTH_CODE}} - Save the
refresh_tokenfrom the response — you'll need it for the config table
Step 2: Create SDP_Config Table in Okta Workflows
Create this table before building any flows. It stores credentials so you never hardcode secrets in cards.
| key | value | Notes |
|---|---|---|
base_url | https://sdpondemand.manageengine.com | US data center |
client_id | Your Zoho OAuth client ID | From api-console.zoho.com |
client_secret | Your Zoho OAuth client secret | From api-console.zoho.com |
refresh_token | Your Zoho refresh token | From Step 1 exchange |
access_token | (initially empty) | Managed by Flow 0 |
token_expires_at | (initially empty) | Managed by Flow 0 |
auth_mode | cloud | Your deployment type |
Download Template Files
Each file is a standalone Markdown template with card-by-card instructions. Right-click → Save As to download.
0 Flow 0: OAuth Token Refresh Helper
Helper flow (no trigger) that ensures a valid Zoho OAuth access token. Called by every other flow before making SDP API requests. Tokens expire every 60 minutes; this flow caches them with a 2-minute buffer.
Card 1: Tables > Read Row — Get Current Token
- Table:
SDP_Config - Where:
keyisaccess_token - Output:
value(current access token)
Card 2: Tables > Read Row — Get Expiry
- Table:
SDP_Config - Where:
keyistoken_expires_at - Output:
value(ISO timestamp)
Card 3: Date > Now
- Output:
current_time(ISO timestamp)
Card 4: Branching > If/Else
- Condition:
card2.valueis aftercard3.current_time - True path: Skip to Card 10 (return cached token)
- False path: Continue to Card 5 (refresh needed)
Cards 5-7: Read OAuth Credentials from Table
- Card 5: Read Row where
key=client_id - Card 6: Read Row where
key=client_secret - Card 7: Read Row where
key=refresh_token
Card 8: HTTP > Raw Request — Call Zoho Token Endpoint
- Method:
POST - URL:
https://accounts.zoho.com/oauth/v2/token - Content-Type:
application/x-www-form-urlencoded
grant_type=refresh_token
&client_id={{card5.value}}
&client_secret={{card6.value}}
&refresh_token={{card7.value}}
Expected response:
{
"access_token": "1000.xxxx.yyyy",
"token_type": "Bearer",
"expires_in": 3600
}
Card 9a: Date > Add — Calculate Expiry
- Add
(expires_in - 120)seconds tocard3.current_time - The 120-second buffer prevents using tokens that are about to expire
Card 9b: Tables > Update Row — Store Token
- Where:
key=access_token - Set:
value=card8.body.access_token
Card 9c: Tables > Update Row — Store Expiry
- Where:
key=token_expires_at - Set:
value=card9a.output
Card 10: Flow Control > Return
- From True path (token valid): return
card1.value - From False path (refreshed): return
card8.body.access_token
Testing
- Clear
access_tokenandtoken_expires_atin SDP_Config table - Run flow — should fetch new token and populate both rows
- Run again immediately — should return cached token (no Zoho call)
- Verify token works: manual GET to
/api/v3/requests
1 Flow 1: Create Ticket SDP API
Creates a ServiceDesk Plus request/ticket from an Okta event. The most common starting flow.
Trigger Options
| Trigger | When it fires | Good for |
|---|---|---|
| Okta > User Assigned to Application | User gets app assigned | Access request tickets |
| Okta > User Deactivated | User is deactivated | Offboarding tickets |
| Okta > User Created | New user in Okta | Onboarding tickets |
| Okta > User Added to Group | Group membership change | Role-based tickets |
| API Endpoint | External webhook call | Custom integrations |
Template trigger: Okta > User Assigned to Application
Available outputs: user.id, user.profile.email, user.profile.firstName, user.profile.lastName, user.profile.department, target.displayName
Card 1: Tables > Read Row — Get Base URL
- Table:
SDP_Config - Where:
keyisbase_url - Output:
https://sdpondemand.manageengine.com
Card 2: Flow Control > Call Flow — Get Token
- Flow:
00-oauth-token-refresh - Output:
access_token
Card 6: Compose — Build Ticket JSON
{
"request": {
"subject": "Application Access: {{trigger.target.displayName}} for {{trigger.user.profile.firstName}} {{trigger.user.profile.lastName}}",
"description": "Automated request: {{trigger.user.profile.firstName}} {{trigger.user.profile.lastName}} ({{trigger.user.profile.email}}) has been assigned to {{trigger.target.displayName}} in Okta. Please provision access in downstream systems.",
"requester": {
"email_id": "{{trigger.user.profile.email}}"
},
"priority": { "name": "Normal" },
"category": { "name": "Access Management" },
"request_type": { "name": "Service Request" },
"group": { "name": "IT Help Desk" }
}
}
Customize: Change category, group, priority, and request_type to match your SDP configuration. Add "technician": {"name": "Jane Doe"} to auto-assign.
Card 7: Text > Encode URI Component + Compose
- Pass Card 6 output through Text > Encode URI Component
- Use Compose to prepend
input_data=
Critical: The SDP V3 API does NOT accept raw JSON bodies. You must send Content-Type: application/x-www-form-urlencoded with the JSON inside input_data=. This is the #1 cause of "400 Bad Request" errors.
Card 8: HTTP > Raw Request — Create Ticket
- Method:
POST - URL:
{{card1.value}}/api/v3/requests - Headers:
Content-Type: application/x-www-form-urlencoded
Authorization: Zoho-oauthtoken {{card2.access_token}}
- Body:
{{card7.output}}(theinput_data=...string)
Card 9: Branching > If/Else — Check Response
- Condition:
card8.statusCodeequals201 - True: Card 10 (success)
- False: Card 11 (error)
Card 10: Success — Log or Notify
- Option A: Create Row in a
Ticket_Logtable withticket_id,subject,user_email,created_at - Option B: Send Slack/Teams message with the new ticket ID
Card 11: Error Handling
- Extract error from
card8.body.response_status.messages - Send error notification via Slack/email
- Consider: retry once on 401 (token may have expired mid-flow)
Simplified Version (Cloud, No Branching)
[Trigger: User Assigned to App]
↓
[Call Flow 0: Get OAuth Token]
↓
[Compose: Build JSON payload]
↓
[Text: Encode URI Component on JSON]
↓
[Compose: Prepend "input_data="]
↓
[HTTP Raw Request]
POST sdpondemand.manageengine.com/api/v3/requests
Header: Authorization: Zoho-oauthtoken {{token}}
Header: Content-Type: application/x-www-form-urlencoded
Body: input_data={{encoded_json}}
↓
[If statusCode == 201 → log success, else → log error]
Testing
- Create a test user in Okta (or use your own in sandbox)
- Assign the test user to an application
- Verify flow triggers and ticket appears in SDP
- Check ticket has correct requester email, subject, category
- Test error: use wrong API key, verify error handling fires
2 Flow 2: Assign / Reassign Ticket Helper
Updates an existing SDP ticket to assign or reassign it to a technician and/or support group. Typically called as a sub-flow from Flow 1 or Flow 4.
Input Parameters
| Parameter | Type | Required | Example |
|---|---|---|---|
request_id | String | Yes | "123456" |
technician_name | String | One of these | "Jane Doe" |
technician_id | String | One of these | "71000000099" |
group_name | String | No | "Tier 2 Support" |
Cards 1-2: Get Base URL + Auth Token
Same as Flow 1: read base_url from table, call Flow 0 for token.
Card 3: Branching — Use ID or Name?
- Condition:
input.technician_idis not empty - True: Build payload with
id - False: Build payload with
name
Cards 4a/4b: Compose — Build JSON
With ID (preferred):
{
"request": {
"technician": { "id": "{{input.technician_id}}" },
"group": { "name": "{{input.group_name}}" }
}
}
With Name:
{
"request": {
"technician": { "name": "{{input.technician_name}}" }
}
}
Cards 5-6: Encode + Form Body
- Text > Encode URI Component on the JSON
- Compose: prepend
input_data=
Card 7: HTTP > Raw Request — Update Ticket
- Method:
PUT - URL:
{{base_url}}/api/v3/requests/{{input.request_id}} - Headers:
Content-Type: application/x-www-form-urlencoded+Authorization: Zoho-oauthtoken - Body:
input_data=...
No separate "assign" endpoint exists. Assignment is just an update (PUT) on the request object. You can also assign at creation time by including technician in the Flow 1 payload.
Card 8: Check Response (statusCode == 200)
Card 9: Return
success: true/falserequest_id: the input request_idmessage: success or error text
Lookup Technician by Email (Optional Pre-Step)
If you only have the user's email from Okta, look up their SDP technician ID first:
GET {{base_url}}/api/v3/technicians?input_data={"list_info":{"search_criteria":[{"field":"email_id","condition":"is","value":"{{email}}"}]}}
// Parse: body.technicians[0].id
Testing
- Create a ticket manually or via Flow 1
- Call this flow with the ticket ID and a valid technician name
- Verify in SDP that the ticket is assigned to that technician
- Test reassignment: call again with a different technician
- Test with invalid technician name — verify error handling
3 Flow 3: Add User to Support Group SDP API
Adds a technician to a support group in SDP. Since the API uses full member replacement (not additive), this flow reads current members, appends the new one, and writes back the full list.
Destructive API: The PUT /support_groups/{id} endpoint replaces the entire members list. If you send only the new member, all existing members are removed. Cards 7-9 below handle this safely.
Prerequisite: Group Mapping Table
Create Okta_SDP_Group_Map table mapping Okta groups to SDP support group IDs:
| okta_group_name | sdp_group_id | sdp_group_name |
|---|---|---|
IT-HelpDesk | 301000000001 | IT Help Desk |
Network-Ops | 301000000002 | Network Operations |
Desktop-Support | 301000000003 | Desktop Support |
Get your group IDs: GET /api/v3/support_groups — the id field in each result.
Card 1: Tables > Read Row — Look Up SDP Group
- Table:
Okta_SDP_Group_Map - Where:
okta_group_nameis{{trigger.target.displayName}} - Output:
sdp_group_id,sdp_group_name
Card 2: Branching — Group Mapping Exists?
- True: Continue
- False: Log "No SDP mapping for Okta group" and end flow
Not every Okta group needs an SDP mapping. This prevents errors for unmapped groups.
Cards 3-4: Auth + Base URL
Call Flow 0 for token, read base_url from table.
Card 5: HTTP > Raw Request — Look Up Technician by Email
GET {{base_url}}/api/v3/technicians?input_data={"list_info":{"search_criteria":[{"field":"email_id","condition":"is","value":"{{trigger.user.profile.email}}"}]}}
// Save: body.technicians[0].id as new_tech_id
Card 6: Branching — Technician Found?
- True: Continue
- False: Log "User is not a technician in SDP" and end
Card 7: HTTP > GET Current Group Members
GET {{base_url}}/api/v3/support_groups/{{sdp_group_id}}
// Parse: body.support_group.members → array of {"id": "...", "name": "..."}
Card 8: List > Find — Already a Member?
- Search
membersfor item whereidequalsnew_tech_id - Found: Log "Already in group" and end (prevents duplicate)
- Not found: Continue
Card 9: List > Map + Append — Build Updated Array
- List > Map existing members to extract just IDs:
{"id": "{{item.id}}"} - List > Append:
{"id": "{{new_tech_id}}"}
Card 10: Compose — Build Update Payload
{
"support_group": {
"members": {{card9.output}}
}
}
Cards 11-12: Encode + Form Body
Same pattern: URL-encode, prepend input_data=
Card 13: HTTP > PUT — Update Support Group
- Method:
PUT - URL:
{{base_url}}/api/v3/support_groups/{{sdp_group_id}}
Card 14: Check Response (statusCode == 200)
- Success: Log "Added {{email}} to {{group_name}}"
- Error: Log error from response
Race condition risk: If two users are added to the same Okta group simultaneously, two flow executions could read the same member list, each append their user, and the second PUT overwrites the first addition.
Mitigations:
- Use Flow Control > Wait to serialize writes to the same group
- Add a periodic reconciliation flow that syncs Okta groups to SDP groups
- Accept the small risk for low-frequency group changes
Testing
- Add Okta-to-SDP group mapping in
Okta_SDP_Group_Maptable - Verify the user exists as a technician in SDP
- Add the user to the mapped Okta group
- Check SDP: user appears in the support group
- Critical: Verify existing members are still present after update
- Test duplicate: add same user again — should no-op
- Test unmapped group — flow should exit cleanly
4 Flow 4: Onboarding Bundle Full Example
End-to-end example: when a new user is created in Okta, this flow creates an onboarding ticket and optionally adds the user to a support group based on department.
Trigger: Okta > User Created
Outputs: user.profile.email, firstName, lastName, department, title, manager
Prerequisite: Dept_Onboarding_Config Table
| department | sdp_category | sdp_group | sdp_priority |
|---|---|---|---|
| Engineering | Onboarding | IT Help Desk | High |
| Sales | Onboarding | IT Help Desk | Normal |
| Finance | Onboarding | IT Help Desk | Normal |
| Executive | Onboarding | Executive Support | Urgent |
Card 1: Read Department Config
- Table:
Dept_Onboarding_Config - Where:
departmentis{{trigger.user.profile.department}}
Card 2: Branching — Department Mapped?
- True: Use mapped values
- False: Defaults: category=Onboarding, group=IT Help Desk, priority=Normal
Card 3: Auth Setup (Call Flow 0 + Read Base URL)
Card 4: Compose — Build Onboarding Ticket
{
"request": {
"subject": "New Hire Onboarding: {{firstName}} {{lastName}} ({{department}})",
"description": "New employee onboarding request.\n\nName: {{firstName}} {{lastName}}\nEmail: {{email}}\nDepartment: {{department}}\nTitle: {{title}}\n\nPlease complete:\n- [ ] Provision laptop\n- [ ] Create email/calendar access\n- [ ] Set up VPN credentials\n- [ ] Provision department-specific apps\n- [ ] Schedule IT orientation",
"requester": { "email_id": "{{email}}" },
"priority": { "name": "{{card1.sdp_priority}}" },
"category": { "name": "{{card1.sdp_category}}" },
"group": { "name": "{{card1.sdp_group}}" },
"request_type": { "name": "Service Request" }
}
}
Cards 5-6: Encode, Build Form Body, POST to /api/v3/requests
Same pattern as Flow 1, Cards 7-8.
Card 7: Save Ticket ID
Compose: extract card6.body.request.id as ticket_id
Card 8: Branching — Auto-Assign?
- True: Call Flow 2 with
ticket_id+ technician info - False: Let SDP auto-assignment handle it
Card 9: Branching — Add to Support Group?
- Condition: Department in [IT, Engineering, Support]
- True: Call Flow 3 or add user to mapped Okta group
- False: Skip
Card 10: (Optional) Parallel Actions
- Path A: Slack/Teams welcome message
- Path B: Calendar event for IT orientation
Card 11: Log to Onboarding_Log Table
user_email,department,ticket_id,sdp_group,created_at,status
[Okta: User Created]
↓
[Read Dept Config]
↓
[Get Auth (Flow 0)]
↓
[Build Ticket JSON]
↓
[Create Ticket via API] → [Save ticket_id]
↓ ↓
[Auto-assign? → Call Flow 2] [Add to team? → Call Flow 3]
↓
[Log to Onboarding_Log]
Variations
- Trigger:
Okta > User Deactivated - Subject:
"Offboarding: {{firstName}} {{lastName}}" - Checklist: Revoke access, collect equipment, disable accounts
- Inverse of Card 9: Remove user from SDP group (filter instead of append)
- Trigger:
Okta > User Assigned to Application - Subject:
"Provision {{app.displayName}} for {{firstName}}" - Category: Map app names to SDP categories via lookup table
Testing
- Create a test user in Okta with a mapped department
- Verify ticket created with correct category, group, priority
- Verify ticket description contains all user details
- Test unmapped department — should use defaults
- If auto-assign enabled, verify technician is set
- Check
Onboarding_Logtable for the entry - Test duplicate user creation — should be idempotent
API Quick Reference
| Operation | Method | Endpoint | Key Fields |
|---|---|---|---|
| Create ticket | POST | /api/v3/requests | subject (required), requester, priority, category, technician, group |
| Update/assign ticket | PUT | /api/v3/requests/{id} | technician, group, status |
| Get ticket | GET | /api/v3/requests/{id} | — |
| List tickets | GET | /api/v3/requests | Use input_data with list_info for filters |
| Update support group | PUT | /api/v3/support_groups/{id} | members array (full replacement!) |
| List technicians | GET | /api/v3/technicians | Filter with search_criteria |
| List support groups | GET | /api/v3/support_groups | Filter with search_criteria |
Authentication (Cloud / US Region)
Authorization: Zoho-oauthtoken {{access_token}}
Content-Type: application/x-www-form-urlencoded
// Base URL: https://sdpondemand.manageengine.com
// Token endpoint: https://accounts.zoho.com/oauth/v2/token
// API console: https://api-console.zoho.com/
Okta Workflows Gotchas
1. The input_data Form Encoding
The SDP V3 API does NOT accept raw JSON. Every request must use application/x-www-form-urlencoded with JSON inside input_data=. Use the "Raw Request" card, not the standard HTTP POST card.
2. OAuth Token Refresh
Zoho access tokens expire every 60 minutes. Okta Workflows doesn't natively manage Zoho tokens. That's why Flow 0 exists — build it first and call it from every other flow.
3. Auth Header Format
Cloud uses Authorization: Zoho-oauthtoken <token> — note: not Bearer. This specific scheme is required.
4. Response Parsing
Successful creates return 201. Updates return 200. Errors return 400/401/500 with response_status.messages array.
5. Rate Limits
SDP Cloud: ~30-50 requests/minute depending on plan. Add a Wait card if you're bulk-creating tickets. Okta Workflows can fire many events rapidly during bulk provisioning.
Master Implementation Checklist
Setup
- Create Zoho Self Client at api-console.zoho.com
- Generate auth code with correct scopes
- Exchange for refresh token
- Create
SDP_Configtable in Okta Workflows - Populate table with client_id, client_secret, refresh_token, base_url
Build Flows
- Build Flow 0: OAuth Token Refresh
- Test Flow 0: verify token fetch and caching
- Build Flow 1: Create Ticket
- Test Flow 1: verify ticket creation in SDP
- Build Flow 2: Assign Ticket
- Test Flow 2: verify assignment and reassignment
- Create
Okta_SDP_Group_Maptable - Build Flow 3: Add User to Team
- Test Flow 3: verify member add with existing members preserved
- Create
Dept_Onboarding_Configtable - Build Flow 4: Onboarding Bundle
- Test Flow 4: end-to-end new user onboarding
Go Live
- Enable flows in production
- Monitor Flow History for errors in first 48 hours
- Document any SDP category/group customizations