Employee Onboarding Workflow

Complete code to onboard new employees with card and PIN in 7 minutes

7 mins
Intermediate

Complete, production-ready code to onboard new employees. Creates person record, assigns to group, issues card and PIN backup.

The Workflow

Scenario: New employee starts Monday. They need permanent building access.

What we'll do

Create person record
Assign to "Employees" group
Issue card credential
Issue PIN credential (backup)
Return credentials to send to employee

Time: About 2-3 seconds per employee

Prerequisites

You need

OAuth access token with account.person and account.person scopes
Employee group ID (usually 1)
Card number from physical card inventory
Credential type IDs (card typically 5, PIN typically 6)

Check your credential types:

bash
curl -X GET "https://api.doorflow.com/api/3/credential_types" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Bash Script

Complete onboarding script you can run from command line:

bash
#!/bin/bash
# Employee Onboarding Script
# Usage: ./onboard_employee.sh "John" "Doe" "john.doe@company.com" "1234567890"

FIRST_NAME=$1
LAST_NAME=$2
EMAIL=$3
CARD_NUMBER=$4

ACCESS_TOKEN="your_access_token_here"
BASE_URL="https://api.doorflow.com/api/3"

echo "Onboarding: $FIRST_NAME $LAST_NAME..."

# Step 1: Create person and assign to Employees group (ID 1)
PERSON_RESPONSE=$(curl -s -X POST "$BASE_URL/people" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{
    \"first_name\": \"$FIRST_NAME\",
    \"last_name\": \"$LAST_NAME\",
    \"email\": \"$EMAIL\",
    \"enabled\": true,
    \"group_ids\": [1]
  }")

PERSON_ID=$(echo $PERSON_RESPONSE | jq -r '.id')

if [ "$PERSON_ID" == "null" ]; then
  echo "Error creating person:"
  echo $PERSON_RESPONSE | jq '.'
  exit 1
fi

echo "[OK] Person created (ID: $PERSON_ID)"

# Step 2: Issue card credential (type ID 5)
CARD_RESPONSE=$(curl -s -X POST "$BASE_URL/people/$PERSON_ID/credentials" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{
    \"person_credential\": {
      \"credential_type_id\": 5,
      \"value\": \"$CARD_NUMBER\",
      \"enabled\": true
    }
  }")

CARD_ID=$(echo $CARD_RESPONSE | jq -r '.id')
echo "[OK] Card issued (ID: $CARD_ID, Number: $CARD_NUMBER)"

# Step 3: Issue auto-generated PIN (backup)
PIN_RESPONSE=$(curl -s -X POST "$BASE_URL/people/$PERSON_ID/credentials" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "person_credential": {
      "credential_type_id": 6,
      "value": "******",
      "enabled": true
    }
  }')

PIN_VALUE=$(echo $PIN_RESPONSE | jq -r '.value')
PIN_ID=$(echo $PIN_RESPONSE | jq -r '.id')
echo "[OK] PIN issued (ID: $PIN_ID, PIN: $PIN_VALUE)"

# Step 4: Verify
VERIFY_RESPONSE=$(curl -s -X GET "$BASE_URL/people/$PERSON_ID" \
  -H "Authorization: Bearer $ACCESS_TOKEN")

echo ""
echo "[OK] Onboarding complete!"
echo ""
echo "Summary:"
echo "  Name: $FIRST_NAME $LAST_NAME"
echo "  Email: $EMAIL"
echo "  Person ID: $PERSON_ID"
echo "  Card Number: $CARD_NUMBER"
echo "  PIN: $PIN_VALUE"
echo ""
echo "Next steps:"
echo "  1. Give employee their physical card (number: $CARD_NUMBER)"
echo "  2. Send PIN via secure channel (SMS/email): $PIN_VALUE"
echo "  3. Test access at a door"

Save as: onboard_employee.sh

Run:

bash
chmod +x onboard_employee.sh
./onboard_employee.sh "Jane" "Smith" "jane@company.com" "9876543210"

JavaScript/Node.js Version

Async function you can integrate into your application:

javascript
async function onboardEmployee(firstName, lastName, email, cardNumber) {
  const accessToken = process.env.DOORFLOW_ACCESS_TOKEN;
  const baseURL = 'https://api.doorflow.com/api/3';

  try {
    // Step 1: Create person
    console.log(`Creating person: ${firstName} ${lastName}...`);

    const personResponse = await fetch(`${baseURL}/people`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${accessToken}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        first_name: firstName,
        last_name: lastName,
        email: email,
        enabled: true,
        group_ids: [1], // Employees group
      }),
    });

    if (!personResponse.ok) {
      const error = await personResponse.json();
      throw new Error(`Failed to create person: ${error.error || error.message}`);
    }

    const person = await personResponse.json();
    console.log(`[OK] Person created (ID: ${person.id})`);

    // Step 2: Issue card
    console.log('Issuing card credential...');

    const cardResponse = await fetch(`${baseURL}/people/${person.id}/credentials`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${accessToken}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        person_credential: {
          credential_type_id: 5, // Card
          value: cardNumber,
          enabled: true,
        },
      }),
    });

    if (!cardResponse.ok) {
      throw new Error(`Failed to issue card: ${await cardResponse.text()}`);
    }

    const card = await cardResponse.json();
    console.log(`[OK] Card issued (Number: ${cardNumber})`);

    // Step 3: Issue auto-generated PIN
    console.log('Issuing backup PIN...');

    const pinResponse = await fetch(`${baseURL}/people/${person.id}/credentials`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${accessToken}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        person_credential: {
          credential_type_id: 6, // PIN
          value: '******', // Auto-generate secure PIN
          enabled: true,
        },
      }),
    });

    if (!pinResponse.ok) {
      throw new Error(`Failed to issue PIN: ${await pinResponse.text()}`);
    }

    const pin = await pinResponse.json();
    console.log(`[OK] PIN issued: ${pin.value}`);

    console.log('[OK] Onboarding complete!');

    return {
      personId: person.id,
      firstName: firstName,
      lastName: lastName,
      email: email,
      cardNumber: cardNumber,
      pin: pin.value,
    };

  } catch (error) {
    console.error('Onboarding failed:', error);
    throw error;
  }
}

// Usage
onboardEmployee('Jane', 'Smith', 'jane@company.com', '9876543210')
  .then(result => {
    console.log('\nOnboarding complete:', result);
    console.log('\nNext steps:');
    console.log('1. Give employee card:', result.cardNumber);
    console.log('2. Send PIN securely:', result.pin);

    // Send welcome email with PIN
    // sendWelcomeEmail(result.email, result.pin);
  })
  .catch(error => {
    console.error('Error:', error.message);
  });

Bulk Onboarding

Onboard multiple employees from CSV:

javascript
const fs = require('fs');
const csv = require('csv-parser');

async function bulkOnboardEmployees(csvFilePath) {
  const employees = [];

  // Read CSV
  await new Promise((resolve, reject) => {
    fs.createReadStream(csvFilePath)
      .pipe(csv())
      .on('data', (row) => {
        employees.push({
          firstName: row.first_name,
          lastName: row.last_name,
          email: row.email,
          cardNumber: row.card_number,
        });
      })
      .on('end', resolve)
      .on('error', reject);
  });

  console.log(`Onboarding ${employees.length} employees...`);

  // Onboard each employee with delay to respect rate limits
  const results = [];
  for (const employee of employees) {
    try {
      const result = await onboardEmployee(
        employee.firstName,
        employee.lastName,
        employee.email,
        employee.cardNumber
      );
      results.push({ success: true, ...result });

      // Delay to avoid rate limits (120 req/minute = ~0.5 seconds per request)
      await new Promise(resolve => setTimeout(resolve, 4000));

    } catch (error) {
      console.error(`Failed to onboard ${employee.firstName} ${employee.lastName}:`, error.message);
      results.push({
        success: false,
        firstName: employee.firstName,
        lastName: employee.lastName,
        error: error.message,
      });
    }
  }

  // Generate report
  const successful = results.filter(r => r.success);
  const failed = results.filter(r => !r.success);

  console.log(`\n[OK] Bulk onboarding complete`);
  console.log(`Successful: ${successful.length}`);
  console.log(`Failed: ${failed.length}`);

  return results;
}

// Usage
bulkOnboardEmployees('./new_employees.csv')
  .then(results => {
    // Save results to file
    fs.writeFileSync(
      './onboarding_results.json',
      JSON.stringify(results, null, 2)
    );
    console.log('Results saved to onboarding_results.json');
  });

CSV format (new_employees.csv):

csv
first_name,last_name,email,card_number
Jane,Smith,jane@company.com,9876543210
Bob,Jones,bob@company.com,1234567890

Error Handling

Always handle errors gracefully:

javascript
async function onboardEmployeeSafely(firstName, lastName, email, cardNumber) {
  try {
    return await onboardEmployee(firstName, lastName, email, cardNumber);
  } catch (error) {
    // Log error
    console.error('Onboarding error:', {
      employee: `${firstName} ${lastName}`,
      email: email,
      error: error.message,
      timestamp: new Date(),
    });

    // Notify admin
    // await notifyAdmin(`Onboarding failed for ${email}: ${error.message}`);

    // Return error result instead of throwing
    return {
      success: false,
      firstName,
      lastName,
      email,
      error: error.message,
    };
  }
}

Common Errors

"Group not found"

  • Group ID doesn't exist
  • Check available groups: GET /api/3/groups

"Duplicate credential value"

  • Card number already in use
  • Verify card number is correct and not already assigned

"Invalid credential_type_id"

  • Credential type not available in your account
  • Check credential types: GET /api/3/credential_types

"Person already exists"

  • Email address already in system
  • Check existing people: GET /api/3/people?email=user@example.com

Best Practices

1. Validate inputs before calling API:

javascript
function validateEmployee(firstName, lastName, email, cardNumber) {
  if (!firstName || !lastName) {
    throw new Error('First and last name required');
  }

  if (!email || !email.includes('@')) {
    throw new Error('Valid email required');
  }

  if (!cardNumber || !/^\d+$/.test(cardNumber)) {
    throw new Error('Valid card number required (numeric only)');
  }
}

2. Always issue backup credentials

Card as primary (fastest access)
PIN as backup (when card is forgotten)
Consider mobile credential for tech-savvy employees

3. Store results for audit trail:

javascript
await db.insert('onboarding_log', {
  person_id: result.personId,
  card_number: result.cardNumber,
  pin: encrypt(result.pin), // Encrypt sensitive data
  onboarded_by: currentUser.id,
  onboarded_at: new Date(),
});

4. Send credentials securely

Email PIN to user's work email
Don't include both card number and PIN in same email
Consider SMS for PIN delivery
Use encrypted channels when possible

5. Test before giving to employee:

javascript
// Verify person can be retrieved
const person = await fetch(`${baseURL}/people/${personId}`, {
  headers: { 'Authorization': `Bearer ${accessToken}` }
});

// Verify credentials exist
const credentials = await fetch(`${baseURL}/people/${personId}/credentials`, {
  headers: { 'Authorization': `Bearer ${accessToken}` }
});

console.log(`Person has ${credentials.length} credentials`);

Integration Examples

Integrate with HR system

javascript
// When employee added in HR system
hrSystem.on('employee.created', async (employee) => {
  try {
    // Get next available card from inventory
    const cardNumber = await cardInventory.getNextAvailableCard();

    // Onboard in DoorFlow
    const result = await onboardEmployee(
      employee.firstName,
      employee.lastName,
      employee.workEmail,
      cardNumber
    );

    // Mark card as issued in inventory
    await cardInventory.markCardIssued(cardNumber, result.personId);

    // Send welcome email with PIN
    await emailService.sendWelcomeEmail(employee.workEmail, {
      cardNumber: cardNumber,
      pin: result.pin,
    });

    console.log(`[OK] ${employee.firstName} ${employee.lastName} onboarded`);

  } catch (error) {
    // Notify HR team of failure
    await hrSystem.addNote(employee.id, `DoorFlow onboarding failed: ${error.message}`);
  }
});

Integrate with Slack

javascript
// Slack command: /onboard Jane Smith jane@company.com 1234567890
slackApp.command('/onboard', async ({ command, ack, respond }) => {
  await ack();

  const [firstName, lastName, email, cardNumber] = command.text.split(' ');

  try {
    const result = await onboardEmployee(firstName, lastName, email, cardNumber);

    await respond({
      text: `${firstName} ${lastName} onboarded successfully!`,
      blocks: [
        {
          type: 'section',
          text: {
            type: 'mrkdwn',
            text: `*Onboarding Complete*\n\nName: ${firstName} ${lastName}\nEmail: ${email}\nCard: ${cardNumber}\nPIN: ||${result.pin}|| (click to reveal)`,
          },
        },
      ],
    });

  } catch (error) {
    await respond({
      text: `Onboarding failed: ${error.message}`,
    });
  }
});

Required OAuth Scopes

  • account.person - Create and manage people
  • account.person - Issue credentials
  • account.group.readonly - Verify groups exist

Next Steps

[Employee Offboarding] - Revoke access when employee leaves
[Visitor Management] - Temporary access for visitors
[Contractor Access] - Time-limited access for contractors

Learn more

[Card Credentials] - Details about card types
[PIN Credentials] - Details about PINs
[Events and Audit Trail] - Monitor employee access