Salesforce

Connects ZombieLicenseHunter to Salesforce to sync user accounts and license assignment data. Two authentication methods are available: JWT Bearer Flow (the form's default), which authenticates server-to-server with a certificate and needs no browser consent once set up, and OAuth 2.0, which sends a Salesforce admin through the browser consent screen and works reliably across all org types including Developer Edition and Agentforce orgs.

Prerequisites

  1. A Salesforce org with System Administrator or equivalent privileges.
  2. A dedicated service account user (e.g., [email protected]) with an active Salesforce license.
  3. Access to Setup in Salesforce to create and configure a Connected App.

Method 1: OAuth 2.0

Step 1: Create a Connected App

Why Connected App, not External Client App? Salesforce offers both options in Setup. External Client Apps use a newer, streamlined UI but only support OAuth 2.0 Authorization Code (PKCE) and Client Credentials flows. ZombieLicenseHunter supports PKCE for OAuth 2.0 authorization code flows, but Connected Apps remain the recommended path because they also support JWT Bearer Flow and are better aligned with the setup documented here.

  1. In Salesforce, go to Setup → Connected Apps → New Connected App.
  2. Fill in:
    • Connected App Name: ZombieLicenseHunter
    • API Name: ZombieLicenseHunter
    • Contact Email: your admin email
  3. Under API (Enable OAuth Settings):
    • Check Enable OAuth Settings.
    • Callback URL: add the following URLs, one per line:
      https://<your-zombielicensehunter-host>/integrations/salesforce/callback
      https://login.salesforce.com/services/oauth2/success
      
    • Selected OAuth Scopes: add Manage user data via APIs (api) and Perform requests at any time (refresh_token, offline_access). These are exactly the scopes ZombieLicenseHunter requests: api, refresh_token and offline_access.
  4. Click Save, then Continue.
  5. Note the Consumer Key and Consumer Secret.

Step 2: Configure Connected App Policies

After the Connected App propagates (up to 10 minutes), configure its policies:

  1. Go to Setup → Connected Apps, find ZombieLicenseHunter, and click Manage.
  2. Click Edit Policies.
  3. Under IP Relaxation, set to Relax IP restrictions. This is required for server-to-server access from ZombieLicenseHunter's infrastructure.
  4. Under OAuth Policies, set Permitted Users to All users may self-authorize.
  5. Click Save.

Step 3: Configure Service Account Permissions

Ensure the service account user's profile has these permissions:

Permission Purpose
API Enabled Required for any API access
View All Users Read user accounts, status, and last login timestamps
View Setup and Configuration Read license assignments and profile/permission set mappings

No write permissions are needed — ZombieLicenseHunter operates in read-only mode.

Step 4: Connect in ZombieLicenseHunter

  1. Navigate to Integrations → Add Integration and select Salesforce. Leave Category as Application.
  2. Under Authentication Method, switch to OAuth 2.0.
  3. Enter:
    • Consumer Key: the Consumer Key (the form labels this field Consumer Key for Salesforce).
    • Client Secret: the Consumer Secret.
  4. Select the Salesforce Environment:
    • Production — for standard production orgs on login.salesforce.com.
    • Sandbox — for sandbox orgs on test.salesforce.com.
    • Custom Domain — for Developer Edition, Agentforce, or orgs with custom My Domain URLs. Enter your Instance URL (e.g., https://yourorg.my.salesforce.com). Find it in Salesforce under Setup → My Domain.
  5. Click Save Integration — ZombieLicenseHunter starts an OAuth 2.0 authorization-code flow with PKCE and redirects you to Salesforce for consent.
  6. After consent, the refresh token is stored automatically and the first sync is queued.

Method 2: JWT Bearer Flow (form default)

Use this for orgs where server-to-server authentication without user interaction is preferred. The JWT audience is login.salesforce.com for Production, test.salesforce.com for Sandbox, and your own My Domain URL for Custom Domain, so orgs that do not authenticate through login.salesforce.com need the Custom Domain environment.

Step 1: Generate a Certificate and Private Key

Generate a self-signed X.509 certificate and PEM private key:

openssl req -x509 -nodes -days 730 -newkey rsa:2048 \
  -keyout zlh_private.pem -out zlh_certificate.crt \
  -subj "/CN=ZombieLicenseHunter"

This produces two files:

  • zlh_certificate.crt — the digital signature (X.509 certificate) to upload to Salesforce.
  • zlh_private.pem — the private key to paste into ZombieLicenseHunter. Keep this secure.

Step 2: Create a Connected App

Follow Method 1, Step 1 above. Additionally:

  1. Go to Setup → Connected Apps, find ZombieLicenseHunter, and click Edit.
  2. Under the API (Enable OAuth Settings) section, check Use digital signatures.
  3. Click Choose File and upload zlh_certificate.crt.
  4. Click Save.

"Use digital signatures" checkbox not visible? This option only appears when editing an existing Connected App, not during initial creation. Save the app first, then edit it to upload the certificate.

Step 3: Configure Connected App Policies

Follow Method 1, Step 2 above. For JWT Bearer, you may additionally need to pre-authorize the user:

  1. Under OAuth Policies, set Permitted Users to Admin approved users are pre-authorized.
  2. Under Profiles or Permission Sets, add the profile assigned to your service account user.

If pre-authorization does not work through the admin UI, perform a one-time browser consent to approve the user for the Connected App. Navigate to:

https://YOUR-SALESFORCE-DOMAIN/services/oauth2/authorize?response_type=token&client_id=YOUR_CONSUMER_KEY&redirect_uri=https://YOUR-SALESFORCE-DOMAIN/services/oauth2/success

Replace YOUR-SALESFORCE-DOMAIN with your org's My Domain URL and YOUR_CONSUMER_KEY with the Consumer Key.

Step 4: Connect in ZombieLicenseHunter

  1. Navigate to Integrations → Add Integration and select Salesforce. Leave Category as Application.
  2. Leave Authentication Method on JWT Bearer Flow.
  3. Enter:
    • Consumer Key: the Consumer Key from your Connected App.
    • Salesforce Username: the service account user's Salesforce username (e.g., [email protected]).
    • Private Key: paste the full contents of zlh_private.pem, including the -----BEGIN and -----END lines.
  4. Select the Salesforce Environment (Production, Sandbox, or Custom Domain with your Instance URL).
  5. Click Save Integration — ZombieLicenseHunter signs a JWT assertion with the key, exchanges it for an access token to verify the credentials, then encrypts and stores them and queues the first sync. Every later sync re-signs the assertion the same way.

What Gets Synced

Data Details
User accounts User ID, username, email, display name, active/inactive status, last login time
License assignments Profile-to-license-type mappings, permission set assignments

Troubleshooting

invalid_grant: audience is invalid The JWT audience does not match your Salesforce org's authentication endpoint. This commonly occurs with Developer Edition, Agentforce, or custom domain orgs that do not authenticate through login.salesforce.com. Select the Custom Domain environment and enter your My Domain URL so the audience matches; if the error persists, use OAuth 2.0 with the same Custom Domain setting.

invalid_grant: user hasn't approved this consumer The user is not pre-authorized for the Connected App. Either set Permitted Users to "All users may self-authorize", or perform the one-time browser consent described in Method 2, Step 3.

invalid_grant: IP restricted The IP Relaxation policy on the Connected App is set to "Enforce IP restrictions". Change it to Relax IP restrictions in the Connected App policies. See Method 1, Step 2.

redirect_uri_mismatch The Callback URL in the Connected App does not include ZombieLicenseHunter's redirect URI. Add https://<your-zombielicensehunter-host>/integrations/salesforce/callback to the Connected App's Callback URL list.

missing required code challenge (PKCE) ZombieLicenseHunter sends PKCE for Salesforce OAuth 2.0 authorization code flows. If Salesforce still reports a missing code challenge, verify you are using the normal OAuth 2.0 setup above and that no proxy is stripping query parameters from the authorize redirect.

invalid_grant: invalid assertion The private key does not match the certificate uploaded to the Connected App. Verify you uploaded zlh_certificate.crt (not the private key) and that the key pair was generated together. Confirm with: openssl x509 -noout -modulus -in zlh_certificate.crt | md5sum should match openssl rsa -noout -modulus -in zlh_private.pem | md5sum.

INSUFFICIENT_ACCESS on API calls The service account user's profile is missing View All Users or View Setup and Configuration permissions.

Certificate expired Generate a new certificate/key pair, upload the new certificate to the Connected App (edit → replace), and update the private key on the integration's edit page in ZombieLicenseHunter.

No users synced after successful connection Confirm the service account user has API Enabled on its profile and that the profile has visibility into the user records you expect to sync.

Connected App changes not taking effect Salesforce can take up to 10 minutes to propagate Connected App changes (new callback URLs, policy changes, certificate uploads). Wait and retry.