Custom User Ingestion with Identity Gateway
Overview
Identity Gateway lets you ingest user identity data from any system that exposes an HTTP API into Moveworks. Use it when your identity source (for example, a ServiceNow HR profile, Workday, or a custom HRIS) is not natively supported as a built-in identity ingestion source.
Setting it up involves three pieces:
- An HTTP Action that calls your API.
- A Crawl Config that handles pagination and extracts records from the response.
- An Identity Ingestion Source that maps the extracted data into the Moveworks User Profile.
Architecture Overview
Data flows through these components in order:
Prerequisites
- A connector configured via HTTP Connectors for the identity platform you’re ingesting.
- An HTTP Action configured in Agent Studio that leverages your connector and fetches users from your identity provider’s API.
Step 1: Create the HTTP Action
Create an HTTP Action that calls your identity source API. The action should:
- Use the Integration linked to your connector.
- Accept pagination parameters as Input Args (for example,
sysparm_offset,sysparm_limit, orpageandpage_size). - Pass those Input Args as query parameters in the request.
- If you are sending parameters in the body, you must use Data Mapper — raw JSON bodies are not currently supported for this use case.
Example: For a ServiceNow HR Profile API, create an action named get_hr_profile with two Input Args: sysparm_offset and sysparm_limit. These get passed as query parameters so the API returns paginated results.
Example API Response
Your HTTP Action should return a JSON response like this:
Note: The exact response structure depends on your system. The key thing is to know (1) the path to the array of user records and (2) the path to each field within a record. You will use this in the next steps.
Step 2: Create the Crawl Config
Create a new Identity Gateway ingestion at User Identity > Identity Gateway > Create New Configuration. This ties together the HTTP Action, pagination logic, and data extraction. It has three sections.
2a. Integration ID
Set this to the same Integration used by your HTTP Action.
2b. Start Request (Initial API Call)
This defines the first API call to kick off ingestion.
Input Args Example — sets the first page to offset 0 with 100 records per page:
Values must be wrapped in single quotes inside the expression (for example, "'0'", not "0"). The single quotes denote a string literal in the Moveworks data mapping language. Also confirm on the HTTP Action that your input args are not hardcoded and are dynamic — your query parameters should look like {{page}} in the HTTP Action.
2c. Response Handler
The Response Handler has two parts: the Output Bender (data extraction) and the Next Request Details (pagination).
Output Bender (Data Extraction)
This extracts user records from the API response and wraps them for the identity pipeline.
Key concepts:
- The API response is available at
parsed_response.value. - Use
MAP()to iterate over the array of records. - Each record must be wrapped in a
record.jsonstructure. - Inside the
MAP(), each array element is referenced asitem. - For response keys containing dots (for example,
user.email), use bracket notation:item["user.email"]. - For regular keys (for example,
employee_number), use dot notation:item.employee_number.
Example Output Bender for the ServiceNow response above:
Example Output Bender if no field filtering is needed:
Breaking this down:
"items": "parsed_response.value.result"— points to the array of user records in the API response."converter"— defines how each record is transformed."record": { "json": { ... } }— required wrapper that packages each record for the identity pipeline.item["user.email"].value— for each record, extracts thevaluefield from theuser.emailobject.item.employee_number.value— same idea for keys without dots in the name.
Common Mistakes:
- Missing the
record.jsonwrapper — records will not be processed without it. - Using
$["key"]instead of["key"]for bracket access on nested objects —$["key"]is only for root-level access. - Using
parsed_response.json.resultinstead ofparsed_response.value.result— the HTTP Action crawler uses thevalueparser, notjson.
Next Request Details (Pagination)
This controls whether the crawler fetches additional pages of results.
Execution Condition — a DSL rule that determines if there is a next page. The crawler stops when this evaluates to false:
This continues paginating as long as the current response returned results. When the API returns an empty array, pagination stops.
Next Request Action — select the same HTTP Action (for example, get_hr_profile).
Next Request Input Args — computes the next page’s parameters based on the previous crawler request:
Key concepts:
- The query parameters you set up in the HTTP Action are accessible via
crawler_request.request. - How you paginate depends on your API. Some APIs follow an “offset” pattern, where the API returns users based on where it left off; others have a configurable page size with page numbers you can key off of.
Special Scenario
If your query parameter starts with $, you need to handle the dot walk in a special manner. For example, to dot-walk to a query param named $skip:
Breaking this down:
crawler_request.request— the previous request’s input args (params, headers, body, path, method).$INTEGER(...)— converts a string to an integer (needed for arithmetic).+— adds the offset and limit to compute the next offset.$TEXT(...)— converts the result back to a string (query params are strings).- The limit is carried forward unchanged from the previous request.
DSL Function Reference:
- All DSL functions use a
$prefix:$LENGTH(),$TEXT(),$INTEGER(), etc. - Arithmetic uses infix operators:
+,-,*,/. - String literals use single quotes:
'hello'. - Use
evalblocks when you need DSL expressions inside a data mapping (Bender) context.
Step 3: Configure the Identity Ingestion Source
Once the Crawl Config is set up, configure the Identity Ingestion to map the extracted data to the Moveworks User Profile.
3a. Add the Source
In the Identity Ingestion Configuration, add a new source:
- Integration ID — select the same Integration used in the Crawl Config and HTTP Action.
- Is Primary Source — set to
trueif this is your main identity source (exactly one source must be primary).
3b. Source-Specific User Attribute Mapping
This maps the fields from the Crawl Config output to Moveworks User Profile fields.
The input to this mapping is the data you defined inside record.json in the Output Bender. Each field is available at the root level — no prefix needed.
Example:
How to read this mapping:
- Left side = Moveworks User Profile field name.
- Right side = field name from your Output Bender’s
record.jsonobject. - Example:
"record_id": "sys_id"means “take thesys_idvalue from the crawled record and store it asrecord_idin the Moveworks User Profile.”
Important:
- Do NOT use
record.oritem.prefixes — the fields are already at the root level when this mapping runs. - The field names on the right must exactly match the keys you defined in the Output Bender’s
record.json.
3c. Joining Key
Set the Joining Key to the field that uniquely identifies users across sources. This is typically email_addr.
If you have multiple identity sources, the Joining Key is used to match and merge user records across sources.
3d. Merge Bender (Multi-Source Only)
If this is your only identity source, leave this empty.
If you have multiple sources, use the Merge Bender to override specific fields from alternate sources. Fields from the primary source are included automatically.
Step 4: Verify the Configuration
After saving all configuration, verify the following:
- The HTTP Action’s Integration ID matches the Integration.
- The Crawl Config’s Integration ID matches the same Integration.
- The Identity Ingestion Source’s Integration ID matches the same Integration.
- Exactly one source is marked as
primary_source = true. - The Joining Key is set and the corresponding field is mapped in the Source Attribute Mapping.
End-to-End Example: ServiceNow HR Profile
Here is a complete configuration example using ServiceNow as the identity source.
HTTP Action: get_hr_profile
Crawl Config
Start Request Input Args:
Output Bender:
Execution Condition:
Next Request Input Args:
Identity Ingestion Source
Source Attribute Mapping:
Joining Key: email_addr
Primary Source: true
Quick Reference: Data Context at Each Stage
Understanding what data is available at each configuration stage is critical for writing correct expressions.
Troubleshooting
DSL validation error: 'no viable alternative at input item.$'
You are using $["key"] syntax on a nested object. The $ prefix for bracket access is only valid at the root level. For nested objects, use plain bracket notation:
- Wrong:
item.$["user.email"].value - Correct:
item["user.email"].value
No users ingested / empty results
Check these common causes:
- Wrong response path — Ensure
itemsin the Output Bender points to the correct array. For HTTP Actions, the path isparsed_response.value.<your_array_key>, NOTparsed_response.json.<your_array_key>orresponse.<your_array_key>. - Execution condition error — If the execution condition references an invalid path (for example,
response.resultinstead ofparsed_response.value.result), it can cause the entire response handler to fail, preventing any records from being extracted. - Missing
record.jsonwrapper — The Output Bender converter must wrap fields inside{ "record": { "json": { ... } } }. Without this wrapper, records will not be processed.
Pagination not working (only first page ingested)
Verify the Next Request Input Args use crawler_request.request to access the input args from the previous request. Common mistakes:
- Wrong:
previous_action.sysparm_offsetorresponse.sysparm_offset - Correct:
crawler_request.request.params.sysparm_offset
Identity mapper returns empty fields
The Source Attribute Mapping receives data at the root level. Do not use prefixes:
- Wrong:
record.email_addroritem.email_addr - Correct:
email_addr
The right-side field names must exactly match the keys you defined in the Output Bender’s record.json.
DSL function errors
All DSL functions require a $ prefix. Common corrections:
- Wrong:
LENGTH(),TEXT(),INTEGER() - Correct:
$LENGTH(),$TEXT(),$INTEGER()
Arithmetic uses infix operators (+, -), not functions:
- Wrong:
$ADD(a, b) - Correct:
a + b