> **Building with AI coding agents?** If you're using an AI coding agent, install the official Scalekit plugin. It gives your agent full awareness of the Scalekit API — reducing hallucinations and enabling faster, more accurate code generation.
>
> - **Claude Code**: `/plugin marketplace add scalekit-inc/claude-code-authstack` then `/plugin install <auth-type>@scalekit-auth-stack`
> - **GitHub Copilot CLI**: `copilot plugin marketplace add scalekit-inc/github-copilot-authstack` then `copilot plugin install <auth-type>@scalekit-auth-stack`
> - **Codex**: run the bash installer, restart, then open Plugin Directory and enable `<auth-type>`
> - **Skills CLI** (Windsurf, Cline, 40+ agents): `npx skills add scalekit-inc/skills --list` then `--skill <skill-name>`
>
> `<auth-type>` / `<skill-name>`: `agent-auth`, `full-stack-auth`, `mcp-auth`, `modular-sso`, `modular-scim` — [Full setup guide](https://docs.scalekit.com/dev-kit/build-with-ai/)

---

# Add Modular SCIM provisioning

This guide shows you how to automate user provisioning with SCIM using Scalekit's Directory API and webhooks. You'll learn to sync user data in real-time, create webhook endpoints for instant updates, and build automated provisioning workflows that keep your application's user data synchronized with your customers' directory providers.

<details>
<summary><IconLucidePlay style="display: inline; width: 1rem; height: 1rem; vertical-align: middle; margin-right: 0.5rem;" /> See the SCIM provisioning in action</summary>

<VideoPlayer link="https://youtu.be/SBJLtQaIbUk" />

</details>

With <a href="/directory/guides/user-provisioning-basics" target="_blank">SCIM Provisioning</a> from Scalekit, you can:

- Use **webhooks** to listen for events from your customers' directory providers (e.g., user updates, group changes)
- Use **REST APIs** to list users, groups, and directories on demand

Scalekit abstracts the complexities of various directory providers, giving you a single interface to automate user lifecycle management. This enables you to create accounts for new hires during onboarding, deactivate accounts when employees depart, and adjust access levels as employees change roles.

<details>
<summary><IconTdesignSequence style="display: inline; width: 1rem; height: 1rem; vertical-align: middle; margin-right: 0.5rem;" /> Review the SCIM provisioning sequence</summary>

```d2 pad=50
title: "SCIM Provisioning Sequence" {
  near: top-center
  shape: text
  style.font-size: 20
}

shape: sequence_diagram

Directory Provider -> Scalekit: User/Group changes occur
Scalekit -> Your App: Webhook events sent \n (user_created, user_updated, etc.)
Your App -> Scalekit: Process webhook and \n verify signature
Your App -> Your App: Update local user database \n (create, update, deactivate)
Your App -> Scalekit: API calls for sync \n (list users, groups)
Scalekit -> Directory Provider: Fetch latest data \n from directory provider
```

</details>

![SCIM Quickstart](@/assets/docs/common/scim-chart.png)

<FoldCard
  title="Build with a coding agent"
  iconKey="build-with-ai"
  iconPosition="right"
  href="/dev-kit/build-with-ai/scim/"
  cta="Build with AI"
  showCta={false}
  clickable={false}
>
  ```bash title="Claude REPL" showLineNumbers=false frame="none"
     /plugin marketplace add scalekit-inc/claude-code-authstack
     ```
     ```bash title="Claude REPL" showLineNumbers=false frame="none"
     /plugin install modular-scim@scalekit-auth-stack
     ```
   ```bash title="Terminal" showLineNumbers=false frame="none"
     curl -fsSL https://raw.githubusercontent.com/scalekit-inc/codex-authstack/main/install.sh | bash
     ```
     ```bash title="Codex" showLineNumbers=false frame="none"
     # Restart Codex
     # Plugin Directory -> Scalekit Auth Stack -> install modular-scim
     ```
   ```bash title="Terminal" showLineNumbers=false frame="none"
     copilot plugin marketplace add scalekit-inc/github-copilot-authstack
     ```
     ```bash title="Terminal" showLineNumbers=false frame="none"
     copilot plugin install modular-scim@scalekit-auth-stack
     ```
   ```bash title="Terminal" showLineNumbers=false frame="none"
     npx skills add scalekit-inc/skills --skill implementing-scim-provisioning
     ```
   [Continue building with AI →](/dev-kit/build-with-ai/scim/)
</FoldCard>

## User provisioning with Scalekit's directory API

Scalekit's directory API allows you to fetch information about users, groups, and directories associated with an organization on-demand. This approach is ideal for scheduled synchronization tasks, bulk data imports, or when you need to ensure your application's user data matches the latest directory provider state.

Let's explore how to use the Directory API to retrieve user and group data programmatically.

1. ### Setting up the SDK
   Before you begin, ensure that your organization <a href="/guides/user-management/scim-provisioning/" target="_blank" rel="noopener noreferrer">has a directory set up in Scalekit</a>.

    Scalekit offers language-specific SDKs for fast SSO integration. Use the installation instructions below for your technology stack:

    <InstallSDK />

    Navigate to **Dashboard > Developers > Settings > API Credentials** to obtain your credentials. Store your credentials securely in environment variables:

    ```shell title=".env"
    # Get these values from Dashboard > Developers > Settings > API Credentials
    SCALEKIT_ENVIRONMENT_URL='https://b2b-app-dev.scalekit.com'
    SCALEKIT_CLIENT_ID='<CLIENT_ID_FROM_SCALEKIT_DASHBOARD>'
    SCALEKIT_CLIENT_SECRET='<SECRET_FROM_SCALEKIT_DASHBOARD>'
    ```

2. ### Initialize the SDK and make your first API call

    Initialize the Scalekit client with your environment variables and make your first API call to list organizations.

    ```bash title="Terminal" wrap
    # Security: Replace <ACCESS_TOKEN> with a valid access token from Scalekit
    # This token authorizes your API requests to access organization data

    # Use case: Verify API connectivity and test authentication
    # Examples: Initial setup testing, debugging integration issues

    curl -L "https://$SCALEKIT_ENVIRONMENT_URL/api/v1/organizations?page_size=5" \
    -H "Authorization: Bearer <ACCESS_TOKEN>"
    ```

    ```javascript title="Node.js" collapse={1-4}
    import { ScalekitClient } from '@scalekit-sdk/node';

    // Initialize Scalekit client with environment variables
    // Security: Always use environment variables for sensitive credentials
    const scalekit = new ScalekitClient(
      process.env.SCALEKIT_ENVIRONMENT_URL,
      process.env.SCALEKIT_CLIENT_ID,
      process.env.SCALEKIT_CLIENT_SECRET,
    );

    try {
      // Use case: Retrieve organizations for bulk user provisioning workflows
      // Examples: Multi-tenant applications, enterprise customer onboarding
      const { organizations } = await scalekit.organization.listOrganization({
        pageSize: 5,
      });

      console.log(`Organization name: ${organizations[0].display_name}`);
      console.log(`Organization ID: ${organizations[0].id}`);
    } catch (error) {
      console.error('Failed to list organizations:', error);
      // Handle error appropriately for your application
    }
    ```

    ```python title="Python" collapse={1-4}
    from scalekit import ScalekitClient
    import os

    # Initialize the SDK client with environment variables
    # Security: Use os.getenv() to securely access credentials
    scalekit_client = ScalekitClient(
        env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"),
        client_id=os.getenv("SCALEKIT_CLIENT_ID"),
        client_secret=os.getenv("SCALEKIT_CLIENT_SECRET")
    )

    try:
        # Use case: Sync user data across multiple organizations
        # Examples: Scheduled provisioning tasks, HR system integration
        org_list = scalekit_client.organization.list_organizations(page_size=100)

        if org_list:
            print(f'Organization details: {org_list[0]}')
            print(f'Organization ID: {org_list[0].id}')
    except Exception as error:
        print(f'Error listing organizations: {error}')
        # Implement appropriate error handling for your use case
    ```

    ```go title="Go" collapse={1-10}
    package main

    import (
        "context"
        "fmt"
        "os"

        "github.com/scalekit/scalekit-go"
    )

    // Initialize Scalekit client with environment variables
    // Security: Always load credentials from environment, not hardcoded
    scalekitClient := scalekit.NewScalekitClient(
        os.Getenv("SCALEKIT_ENVIRONMENT_URL"),
        os.Getenv("SCALEKIT_CLIENT_ID"),
        os.Getenv("SCALEKIT_CLIENT_SECRET"),
    )

    // Use case: Get specific organization for directory sync operations
    // Examples: Targeted user provisioning, organization-specific workflows
    organization, err := scalekitClient.Organization.GetOrganization(
        ctx,
        organizationId,
    )
    if err != nil {
        // Handle error appropriately for your application
        return fmt.Errorf("failed to get organization: %w", err)
    }
    ```

    ```java title="Java" collapse={1-8}
    import com.scalekit.ScalekitClient;

    // Initialize Scalekit client with environment variables
    // Security: Use System.getenv() to securely access credentials
    ScalekitClient scalekitClient = new ScalekitClient(
        System.getenv("SCALEKIT_ENVIRONMENT_URL"),
        System.getenv("SCALEKIT_CLIENT_ID"),
        System.getenv("SCALEKIT_CLIENT_SECRET")
    );

    try {
        // Use case: List organizations for automated provisioning workflows
        // Examples: Enterprise customer setup, multi-tenant management
        ListOrganizationsResponse organizations = scalekitClient.organizations()
            .listOrganizations(5, "");

        if (!organizations.getOrganizations().isEmpty()) {
            Organization firstOrg = organizations.getOrganizations().get(0);
            System.out.println("Organization name: " + firstOrg.getDisplayName());
            System.out.println("Organization ID: " + firstOrg.getId());
        }
    } catch (ScalekitException error) {
        System.err.println("Failed to list organizations: " + error.getMessage());
        // Implement appropriate error handling
    }
    ```

4. ### Retrieve a directory

    After successfully listing organizations, you'll need to retrieve the specific directory to begin syncing user and group data. You can retrieve directories using either the organization and directory IDs, or fetch the primary directory for an organization.

    ```javascript title="Node.js"
    try {
      // Use case: Get specific directory when organization has multiple directories
      // Examples: Department-specific provisioning, multi-division companies
      const { directory } = await scalekit.directory.getDirectory('<organization_id>', '<directory_id>');
      console.log(`Directory name: ${directory.name}`);

      // Use case: Get primary directory for simple provisioning workflows
      // Examples: Small organizations, single-directory setups
      const { directory } = await scalekit.directory.getPrimaryDirectoryByOrganizationId('<organization_id>');
      console.log(`Primary directory ID: ${directory.id}`);
    } catch (error) {
      console.error('Failed to retrieve directory:', error);
      // Handle error appropriately for your application
    }
    ```

    ```python title="Python"
    try:
        # Use case: Access specific directory for targeted user sync operations
        # Examples: Regional offices, business unit-specific provisioning
        directory = scalekit_client.directory.get_directory(
            organization_id='<organization_id>', directory_id='<directory_id>'
        )
        print(f'Directory name: {directory.name}')

        # Use case: Get primary directory for streamlined user management
        # Examples: Standard employee provisioning, main company directory
        primary_directory = scalekit_client.directory.get_primary_directory_by_organization_id(
            organization_id='<organization_id>'
        )
        print(f'Primary directory ID: {primary_directory.id}')
    except Exception as error:
        print(f'Error retrieving directory: {error}')
        # Implement appropriate error handling
    ```

    ```go title="Go"
    // Use case: Retrieve specific directory for granular access control
    // Examples: Multi-tenant environments, department-level provisioning
    directory, err := scalekitClient.Directory().GetDirectory(ctx, organizationId, directoryId)
    if err != nil {
        return fmt.Errorf("failed to get directory: %w", err)
    }
    fmt.Printf("Directory name: %s\n", directory.Name)

    // Use case: Get primary directory for simplified user management
    // Examples: Automated provisioning workflows, bulk user imports
    directory, err := scalekitClient.Directory().GetPrimaryDirectoryByOrganizationId(ctx, organizationId)
    if err != nil {
        return fmt.Errorf("failed to get primary directory: %w", err)
    }
    fmt.Printf("Primary directory ID: %s\n", directory.ID)
    ```

    ```java title="Java"
    try {
        // Use case: Access specific directory for detailed user management
        // Examples: Custom provisioning logic, directory-specific rules
        Directory directory = scalekitClient.directories()
            .getDirectory("<directoryId>", "<organizationId>");
        System.out.println("Directory name: " + directory.getName());

        // Use case: Get primary directory for standard provisioning workflows
        // Examples: Employee onboarding, automated user sync
        Directory primaryDirectory = scalekitClient.directories()
            .getPrimaryDirectoryByOrganizationId("<organizationId>");
        System.out.println("Primary directory ID: " + primaryDirectory.getId());
    } catch (ScalekitException error) {
        System.err.println("Failed to retrieve directory: " + error.getMessage());
        // Implement appropriate error handling
    }
    ```

5. ### List users in a directory

    Once you have the directory information, you can fetch users within that directory. This is commonly used for bulk user synchronization and maintaining an up-to-date user database.

    ```javascript title="Node.js"
    try {
      // Use case: Bulk user synchronization and provisioning
      // Examples: New customer onboarding, scheduled user data sync
      const { users } = await scalekit.directory.listDirectoryUsers('<organization_id>', '<directory_id>');

      // Process each user for provisioning or updates
      users.forEach(user => {
        console.log(`User email: ${user.email}, Name: ${user.name}`);
        // TODO: Implement your user provisioning logic here
      });
    } catch (error) {
      console.error('Failed to list directory users:', error);
      // Handle error appropriately for your application
    }
    ```

    ```python title="Python"
    try:
        # Use case: Automated user provisioning workflows
        # Examples: HR system integration, bulk user imports
        directory_users = scalekit_client.directory.list_directory_users(
            organization_id='<organization_id>', directory_id='<directory_id>'
        )

        # Process each user for local database updates
        for user in directory_users:
            print(f'User email: {user.email}, Name: {user.name}')
            # TODO: Implement your user synchronization logic here
    except Exception as error:
        print(f'Error listing directory users: {error}')
        # Implement appropriate error handling
    ```

    ```go title="Go"
    // Configure pagination options for large user directories
    options := &ListDirectoryUsersOptions{
        PageSize: 50,  // Adjust based on your needs
        PageToken: "",
    }

    // Use case: Paginated user retrieval for large directories
    // Examples: Enterprise customer provisioning, regular sync jobs
    directoryUsers, err := scalekitClient.Directory().ListDirectoryUsers(ctx, organizationId, directoryId, options)
    if err != nil {
        return fmt.Errorf("failed to list directory users: %w", err)
    }

    // Process each user
    for _, user := range directoryUsers.Users {
        fmt.Printf("User email: %s, Name: %s\n", user.Email, user.Name)
        // TODO: Implement your user provisioning logic
    }
    ```

    ```java title="Java"
    // Configure options for user listing with pagination
    var options = ListDirectoryResourceOptions.builder()
        .pageSize(50)  // Adjust based on your requirements
        .pageToken("")
        .includeDetail(true)  // Include detailed user information
        .build();

    try {
        // Use case: Enterprise user management and synchronization
        // Examples: Scheduled sync tasks, user provisioning automation
        ListDirectoryUsersResponse usersResponse = scalekitClient.directories()
            .listDirectoryUsers(directory.getId(), organizationId, options);

        // Process each user for provisioning
        for (User user : usersResponse.getUsers()) {
            System.out.println("User email: " + user.getEmail() + ", Name: " + user.getName());
            // TODO: Implement your user provisioning logic here
        }
    } catch (ScalekitException error) {
        System.err.println("Failed to list directory users: " + error.getMessage());
        // Implement appropriate error handling
    }
    ```
**Customer onboarding use case:** When setting up a new customer account, use the `listDirectoryUsers` function to automatically connect to their directory and start syncing user data. This enables immediate user provisioning without manual user creation.

6. ### List groups in a directory

    Groups are essential for implementing role-based access control (RBAC) in your application. After retrieving users, you can fetch groups to manage permissions and access levels based on organizational structure.

    ```javascript title="Node.js"
    try {
      // Use case: Role-based access control implementation
      // Examples: Department-level permissions, project-based access
      const { groups } = await scalekit.directory.listDirectoryGroups(
        '<organization_id>',
        '<directory_id>',
      );

      // Process each group for RBAC setup
      groups.forEach(group => {
        console.log(`Group name: ${group.name}, ID: ${group.id}`);
        // TODO: Implement your group-based permission logic here
      });
    } catch (error) {
      console.error('Failed to list directory groups:', error);
      // Handle error appropriately for your application
    }
    ```

    ```python title="Python"
    try:
        # Use case: Department-based access control
        # Examples: Engineering vs Sales permissions, project team access
        directory_groups = scalekit_client.directory.list_directory_groups(
            directory_id='<directory_id>', organization_id='<organization_id>'
        )

        # Process each group for permission mapping
        for group in directory_groups:
            print(f'Group name: {group.name}, ID: {group.id}')
            # TODO: Implement your group-based permission logic here
    except Exception as error:
        print(f'Error listing directory groups: {error}')
        # Implement appropriate error handling
    ```

    ```go title="Go"
    // Configure pagination for group listing
    options := &ListDirectoryGroupsOptions{
        PageSize: 25,  // Adjust based on expected group count
        PageToken: "",
    }

    // Use case: Organizational role management
    // Examples: Enterprise role hierarchy, department-based access
    directoryGroups, err := scalekitClient.Directory().ListDirectoryGroups(ctx, organizationId, directoryId, options)
    if err != nil {
        return fmt.Errorf("failed to list directory groups: %w", err)
    }

    // Process each group for RBAC implementation
    for _, group := range directoryGroups.Groups {
        fmt.Printf("Group name: %s, ID: %s\n", group.Name, group.ID)
        // TODO: Implement your group-based permission logic
    }
    ```

    ```java title="Java"
    // Configure options for detailed group information
    var options = ListDirectoryResourceOptions.builder()
        .pageSize(25)  // Adjust based on your requirements
        .pageToken("")
        .includeDetail(true)  // Include group membership details
        .build();

    try {
        // Use case: Enterprise permission management
        // Examples: Role assignments, access level configurations
        ListDirectoryGroupsResponse groupsResponse = scalekitClient.directories()
            .listDirectoryGroups(directory.getId(), organizationId, options);

        // Process each group for permission mapping
        for (Group group : groupsResponse.getGroups()) {
            System.out.println("Group name: " + group.getName() + ", ID: " + group.getId());
            // TODO: Implement your group-based permission logic here
        }
    } catch (ScalekitException error) {
        System.err.println("Failed to list directory groups: " + error.getMessage());
        // Implement appropriate error handling
    }
    ```
**Role-based access control:** Use group information to implement role-based access control in your application. Map directory groups to application roles and permissions to automatically assign access levels based on a user's organizational memberships.

    Scalekit's Directory API provides a simple way to fetch user and group information on-demand. Refer to our [API reference](https://docs.scalekit.com/apis/) to explore more capabilities.

## Realtime user provisioning with webhooks

While the Directory API is perfect for scheduled synchronization, webhooks enable immediate, real-time user provisioning. When directory providers send events to Scalekit, we forward them instantly to your application, allowing you to respond to user changes as they happen.

This approach is ideal for scenarios requiring immediate action, such as new employee onboarding or emergency access revocation.

1. ### Create a secure webhook endpoint

    Create a webhook endpoint to receive real-time events from directory providers. After implementing your endpoint, register it in **Dashboard > Webhooks** where you'll receive a secret for payload verification.
**Critical security requirement:** Always verify webhook signatures before processing events. This prevents unauthorized parties from triggering your provisioning logic and protects against replay attacks.

    ```javascript title="Express.js"
    app.post('/webhook', async (req, res) => {
      // Security: ALWAYS verify requests are from Scalekit before processing
      // This prevents unauthorized parties from triggering your provisioning logic

      const event = req.body;
      const headers = req.headers;
      const secret = process.env.SCALEKIT_WEBHOOK_SECRET;

      try {
        // Verify webhook signature to prevent replay attacks and forged requests
        await scalekit.verifyWebhookPayload(secret, headers, event);
      } catch (error) {
        console.error('Webhook signature verification failed:', error);
        // Return 400 for invalid signatures - this prevents processing malicious requests
        return res.status(400).json({ error: 'Invalid signature' });
      }

      try {
        // Use case: Real-time user provisioning based on directory events
        // Examples: New hire onboarding, emergency access revocation, role changes
        const { email, name } = event.data;

        // Process the webhook event based on its type
        switch (event.type) {
          case 'organization.directory.user_created':
            await createUserAccount(email, name);
            break;
          case 'organization.directory.user_updated':
            await updateUserAccount(email, name);
            break;
          case 'organization.directory.user_deleted':
            await deactivateUserAccount(email);
            break;
          default:
            console.log(`Unhandled event type: ${event.type}`);
        }

        res.status(201).json({ message: 'Webhook processed successfully' });
      } catch (processingError) {
        console.error('Failed to process webhook event:', processingError);
        res.status(500).json({ error: 'Processing failed' });
      }
    });
    ```

    ```python title="FastAPI"
    from fastapi import FastAPI, Request, HTTPException
    import os
    import json

    app = FastAPI()

    @app.post("/webhook")
    async def api_webhook(request: Request):
        # Security: ALWAYS verify webhook signatures before processing events
        # This prevents unauthorized webhook calls and replay attacks

        headers = request.headers
        body = await request.json()

        try:
            # Verify webhook payload using the secret from Scalekit dashboard
            # Get this from Dashboard > Webhooks after registering your endpoint
            is_valid = scalekit_client.verify_webhook_payload(
                secret=os.getenv("SCALEKIT_WEBHOOK_SECRET"),
                headers=headers,
                payload=json.dumps(body).encode('utf-8')
            )

            if not is_valid:
                raise HTTPException(status_code=400, detail="Invalid webhook signature")

        except Exception as verification_error:
            print(f"Webhook verification failed: {verification_error}")
            raise HTTPException(status_code=400, detail="Webhook verification failed")

        # Use case: Instant user provisioning based on directory events
        # Examples: Automated onboarding, immediate access revocation, role updates
        try:
            event_type = body.get("type")
            event_data = body.get("data", {})
            email = event_data.get("email")
            name = event_data.get("name")

            if event_type == "organization.directory.user_created":
                await create_user_account(email, name)
            elif event_type == "organization.directory.user_updated":
                await update_user_account(email, name)
            elif event_type == "organization.directory.user_deleted":
                await deactivate_user_account(email)

            return JSONResponse(status_code=201, content={"status": "processed"})

        except Exception as processing_error:
            print(f"Failed to process webhook: {processing_error}")
            raise HTTPException(status_code=500, detail="Event processing failed")
    ```

    ```java title="Spring Boot"
    @PostMapping("/webhook")
    public ResponseEntity<String> webhook(
            @RequestBody String body,
            @RequestHeader Map<String, String> headers) {

        // Security: ALWAYS verify webhook signatures before processing
        // This prevents malicious webhook calls and protects against replay attacks

        String secret = System.getenv("SCALEKIT_WEBHOOK_SECRET");

        try {
            // Verify webhook signature using Scalekit SDK
            boolean isValid = scalekitClient.webhook()
                .verifyWebhookPayload(secret, headers, body.getBytes());

            if (!isValid) {
                return ResponseEntity.badRequest().body("Invalid webhook signature");
            }

        } catch (Exception verificationError) {
            System.err.println("Webhook verification failed: " + verificationError.getMessage());
            return ResponseEntity.badRequest().body("Webhook verification failed");
        }

        try {
            // Use case: Real-time user lifecycle management
            // Examples: Employee onboarding, access termination, role modifications
            ObjectMapper mapper = new ObjectMapper();
            JsonNode rootNode = mapper.readTree(body);

            String eventType = rootNode.get("type").asText();
            JsonNode data = rootNode.get("data");

            switch (eventType) {
                case "organization.directory.user_created":
                    String email = data.get("email").asText();
                    String name = data.get("name").asText();
                    createUserAccount(email, name);
                    break;
                case "organization.directory.user_updated":
                    updateUserAccount(data);
                    break;
                case "organization.directory.user_deleted":
                    deactivateUserAccount(data.get("email").asText());
                    break;
                default:
                    System.out.println("Unhandled event type: " + eventType);
            }

            return ResponseEntity.status(HttpStatus.CREATED).body("Webhook processed");

        } catch (Exception processingError) {
            System.err.println("Failed to process webhook event: " + processingError.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body("Event processing failed");
        }
    }
    ```

    ```go title="Go"
    // Security: Store webhook secret securely in environment variables
    // Get this from Dashboard > Webhooks after registering your endpoint
    webhookSecret := os.Getenv("SCALEKIT_WEBHOOK_SECRET")

    http.HandleFunc("/webhook", func(w http.ResponseWriter, r *http.Request) {
        // Security: ALWAYS verify webhook signatures before processing events
        // This prevents unauthorized webhook calls and replay attacks

        if r.Method != http.MethodPost {
            http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
            return
        }

        body, err := io.ReadAll(r.Body)
        if err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
            return
        }
        defer r.Body.Close()

        // Extract webhook headers for verification
        headers := map[string]string{
            "webhook-id":        r.Header.Get("webhook-id"),
            "webhook-signature": r.Header.Get("webhook-signature"),
            "webhook-timestamp": r.Header.Get("webhook-timestamp"),
        }

        // Verify webhook signature to prevent malicious requests
        _, err = scalekitClient.VerifyWebhookPayload(webhookSecret, headers, body)
        if err != nil {
            http.Error(w, "Invalid webhook signature", http.StatusBadRequest)
            return
        }

        // Use case: Instant user provisioning and lifecycle management
        // Examples: Real-time onboarding, emergency access revocation, role synchronization
        var webhookEvent WebhookEvent
        if err := json.Unmarshal(body, &webhookEvent); err != nil {
            http.Error(w, "Invalid webhook payload", http.StatusBadRequest)
            return
        }

        switch webhookEvent.Type {
        case "organization.directory.user_created":
            err = createUserAccount(webhookEvent.Data.Email, webhookEvent.Data.Name)
        case "organization.directory.user_updated":
            err = updateUserAccount(webhookEvent.Data)
        case "organization.directory.user_deleted":
            err = deactivateUserAccount(webhookEvent.Data.Email)
        default:
            fmt.Printf("Unhandled event type: %s\n", webhookEvent.Type)
        }

        if err != nil {
            http.Error(w, "Failed to process webhook", http.StatusInternalServerError)
            return
        }

        w.WriteHeader(http.StatusCreated)
        w.Write([]byte(`{"status": "processed"}`))
    })
    ```
**Webhook endpoint example:** A typical webhook endpoint URL would be: `https://your-app.com/api/webhooks/scalekit`. Ensure this URL is publicly accessible and uses HTTPS for security.

2. ### Register your webhook endpoint

    After implementing your secure webhook endpoint, register it in the Scalekit dashboard to start receiving events:

1. Navigate to **Dashboard > Webhooks**
2. Click **+Add Endpoint**
3. Enter your webhook endpoint URL (e.g., `https://your-app.com/api/webhooks/scalekit`)
4. Add a meaningful description for your reference
5. Select the event types you want to receive. Common choices include:
   - `organization.directory.user_created` - New user provisioning
       - `organization.directory.user_updated` - User profile changes
       - `organization.directory.user_deleted` - User deactivation
       - `organization.directory.group_created` - New group creation
       - `organization.directory.group_updated` - Group modifications

    Once registered, your webhook endpoint will start receiving event payloads from directory providers in real-time.
**Testing webhooks:** Use request bin services like Beeceptor or webhook.site for initial testing. Refer to our [webhook setup guide](/directory/reference/directory-events/) for detailed testing instructions.

3. ### Process webhook events

    Scalekit standardizes event payloads across different directory providers, ensuring consistent data structure regardless of whether your customers use Azure AD, Okta, Google Workspace, or other providers.

    When directory changes occur, Scalekit sends events with the following structure:

    ```json title="Webhook event payload"
    {
      "id": "evt_1234567890",
      "type": "organization.directory.user_created",
      "data": {
        "email": "john.doe@company.com",
        "name": "John Doe",
        "organization_id": "org_12345",
        "directory_id": "dir_67890"
      },
      "timestamp": "2024-01-15T10:30:00Z"
    }
    ```
**Webhook delivery and retry policy:** Scalekit attempts webhook delivery using an exponential backoff retry policy until we receive a successful 200/201 response code from your servers:

    | Attempt | Timing |
    |---------|--------|
    | 1 | Immediately |
    | 2 | 5 seconds |
    | 3 | 5 minutes |
    | 4 | 30 minutes |
    | 5 | 2 hours |
    | 6 | 5 hours |
    | 7 | 10 hours |
    | 8 | 10 hours |

    You have now successfully implemented and registered a webhook endpoint, enabling your application to receive real-time events for automated user provisioning. Your system can now respond instantly to directory changes, providing seamless user lifecycle management.

    Refer to our [webhook implementation guide](/authenticate/implement-workflows/implement-webhooks/) for the complete list of available event types and payload structures.

---

## More Scalekit documentation

| Resource | What it contains | When to use it |
|----------|-----------------|----------------|
| [/llms.txt](/llms.txt) | Structured index with routing hints per product area | Start here — find which documentation set covers your topic before loading full content |
| [/llms-full.txt](/llms-full.txt) | Complete documentation for all Scalekit products in one file | Use when you need exhaustive context across multiple products or when the topic spans several areas |
| [sitemap-0.xml](https://docs.scalekit.com/sitemap-0.xml) | Full URL list of every documentation page | Use to discover specific page URLs you can fetch for targeted, page-level answers |
