Files
blackroad-operating-system/sop/integrations/github-to-salesforce.md
Claude 7cde897040 Add complete automation SOP system for BlackRoad ERP
This implements the "Automate The Company" initiative with comprehensive
Standard Operating Procedures for GitHub + Salesforce + Asana integration.

New directory: sop/
├── workflows/ - End-to-end process documentation
│   ├── new-client-kickoff.md - Flagship workflow from deal → repos → Asana
│   └── release-pipeline.md - Deploy → update Salesforce + Asana
├── playbooks/ - Human-friendly checklists
│   └── brenda-new-client-checklist.md - Non-technical operator guide
├── salesforce/ - Salesforce automation specifications
│   ├── flows/opp-automation-onstagechange.md - Trigger on Closed Won
│   └── orchestrations/new-client-kickoff-orchestration.md - Multi-stage process
├── integrations/ - API integration specifications
│   ├── salesforce-to-github.md - Create repos from Salesforce
│   ├── github-to-salesforce.md - Update Salesforce after deploy
│   └── salesforce-to-asana.md - Create Asana projects from Salesforce
└── templates/ - Reusable templates
    ├── github-actions/ - CI/CD workflows (ci.yml, deploy.yml, safety.yml)
    └── repo-template/ - Standard repo config (PR template, labels, branch protection)

Key Features:
- Event-driven automation (Closed Won → repos + Asana creation)
- GitHub Actions templates for CI/CD baseline
- Salesforce Flow & Orchestration specs
- Complete API integration documentation
- Operator-friendly playbooks
- Two-view approach (operator + engineer)
- No manual status syncing across systems

This provides the complete backbone for next-gen ERP automation.
2025-11-17 08:17:51 +00:00

17 KiB

Integration: GitHub → Salesforce

Purpose: Enable GitHub Actions to update Salesforce records after deployments Direction: GitHub calls Salesforce REST API Authentication: OAuth 2.0 (Connected App) or Named Credential Status: Active Last Updated: 2025-11-17


Overview

This integration allows GitHub Actions workflows to:

  • Update Project records with deployment metadata
  • Create Deployment records for audit trail
  • Trigger Salesforce flows/automations
  • Close tasks or update statuses

Key Use Case: Auto-update Salesforce when code is deployed to production


Authentication Setup

Benefits:

  • OAuth 2.0 standard
  • Refresh tokens
  • IP restrictions
  • Better audit trail

Setup Steps:

1. Create Connected App in Salesforce

  1. Setup → App Manager → New Connected App

  2. Basic Information:

    • Connected App Name: GitHub Actions Integration
    • API Name: GitHub_Actions_Integration
    • Contact Email: devops@blackroad.com
  3. API (Enable OAuth Settings):

    • Enable OAuth Settings
    • Callback URL: https://login.salesforce.com/services/oauth2/callback
    • Selected OAuth Scopes:
      • api - Perform requests at any time
      • refresh_token, offline_access - Perform requests at any time
  4. Save and wait 2-10 minutes for Consumer Key/Secret to be generated

  5. Get Credentials:

    • Consumer Key (Client ID): 3MVG9...
    • Consumer Secret (Client Secret): ABC123...

2. Create GitHub Secrets

In each project repository (or organization-level):

  1. Go to: Settings → Secrets and variables → Actions
  2. Add these secrets:
Secret Name Value Description
SALESFORCE_INSTANCE_URL https://your-domain.my.salesforce.com Salesforce instance URL
SALESFORCE_CLIENT_ID 3MVG9... Connected App Consumer Key
SALESFORCE_CLIENT_SECRET ABC123... Connected App Consumer Secret
SALESFORCE_USERNAME integration-user@blackroad.com Service account username
SALESFORCE_PASSWORD password123 Service account password
SALESFORCE_SECURITY_TOKEN XYZ789... Security token for user

Security Best Practice: Use a dedicated integration service account, not a personal user.

3. Get Access Token in GitHub Actions

Workflow Step:

- name: Get Salesforce Access Token
  id: sf-auth
  run: |
    RESPONSE=$(curl -X POST "https://login.salesforce.com/services/oauth2/token" \
      -d "grant_type=password" \
      -d "client_id=${{ secrets.SALESFORCE_CLIENT_ID }}" \
      -d "client_secret=${{ secrets.SALESFORCE_CLIENT_SECRET }}" \
      -d "username=${{ secrets.SALESFORCE_USERNAME }}" \
      -d "password=${{ secrets.SALESFORCE_PASSWORD }}${{ secrets.SALESFORCE_SECURITY_TOKEN }}")

    ACCESS_TOKEN=$(echo $RESPONSE | jq -r '.access_token')
    INSTANCE_URL=$(echo $RESPONSE | jq -r '.instance_url')

    echo "ACCESS_TOKEN=$ACCESS_TOKEN" >> $GITHUB_OUTPUT
    echo "INSTANCE_URL=$INSTANCE_URL" >> $GITHUB_OUTPUT

Option B: Salesforce REST API Endpoint (Webhook Style)

Setup Steps:

  1. Create Apex REST Endpoint:
@RestResource(urlMapping='/github/webhook')
global class GitHubWebhookHandler {

    @HttpPost
    global static String handleDeployment() {
        RestRequest req = RestContext.request;
        String body = req.requestBody.toString();

        Map<String, Object> payload = (Map<String, Object>) JSON.deserializeUntyped(body);

        String projectKey = (String) payload.get('project_key');
        String deployedAt = (String) payload.get('deployed_at');
        String gitSHA = (String) payload.get('git_sha');
        String deployedBy = (String) payload.get('deployed_by');

        // Find Project by external ID
        Project__c project = [
            SELECT Id, Name
            FROM Project__c
            WHERE Project_Key__c = :projectKey
            LIMIT 1
        ];

        // Update Project
        project.Last_Deploy_At__c = Datetime.valueOf(deployedAt);
        project.Last_Deploy_SHA__c = gitSHA;
        project.Last_Deploy_Actor__c = deployedBy;
        project.Deploy_Status__c = 'Success';
        update project;

        // Create Deployment record
        Deployment__c deployment = new Deployment__c(
            Project__c = project.Id,
            Deployed_At__c = Datetime.valueOf(deployedAt),
            Git_SHA__c = gitSHA,
            Deployed_By__c = deployedBy,
            Status__c = 'Success'
        );
        insert deployment;

        return JSON.serialize(new Map<String, Object>{
            'success' => true,
            'project_id' => project.Id,
            'deployment_id' => deployment.Id
        });
    }
}
  1. Configure Site Guest User Permissions:

    • Grant access to Project__c and Deployment__c objects
    • Or use API key authentication in Apex
  2. Get Endpoint URL:

    • https://your-domain.my.salesforce.com/services/apexrest/github/webhook
  3. Add to GitHub Secrets:

    • SALESFORCE_WEBHOOK_URL: Full endpoint URL

API Operations

1. Update Project Record (Upsert by External ID)

Endpoint:

PATCH {INSTANCE_URL}/services/data/v58.0/sobjects/Project__c/Project_Key__c/{PROJECT_KEY}

Headers:

Authorization: Bearer {ACCESS_TOKEN}
Content-Type: application/json

Payload:

{
  "Last_Deploy_At__c": "2025-11-17T14:30:00Z",
  "Last_Deploy_SHA__c": "a1b2c3d4e5f6",
  "Last_Deploy_Branch__c": "main",
  "Last_Deploy_Actor__c": "github-user",
  "Deploy_Status__c": "Success",
  "Environment__c": "Production",
  "Backend_URL__c": "https://backend.railway.app",
  "Release_Notes_URL__c": "https://github.com/org/repo/commit/a1b2c3d4e5f6"
}

Response (200 OK or 201 Created):

{
  "id": "a0X5e000000XYZ1EAO",
  "success": true,
  "errors": []
}

GitHub Actions Implementation:

- name: Update Salesforce Project record
  env:
    SF_INSTANCE_URL: ${{ steps.sf-auth.outputs.INSTANCE_URL }}
    SF_ACCESS_TOKEN: ${{ steps.sf-auth.outputs.ACCESS_TOKEN }}
  run: |
    # Extract PROJECT_KEY from repo name
    REPO_NAME="${{ github.repository }}"
    PROJECT_KEY=$(echo "$REPO_NAME" | sed -n 's/.*blackroad-\([A-Z0-9-]*\)-.*/\1/p')

    curl -X PATCH \
      "$SF_INSTANCE_URL/services/data/v58.0/sobjects/Project__c/Project_Key__c/$PROJECT_KEY" \
      -H "Authorization: Bearer $SF_ACCESS_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "Last_Deploy_At__c": "'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'",
        "Last_Deploy_SHA__c": "${{ github.sha }}",
        "Last_Deploy_Branch__c": "${{ github.ref_name }}",
        "Last_Deploy_Actor__c": "${{ github.actor }}",
        "Deploy_Status__c": "Success",
        "Environment__c": "Production",
        "Release_Notes_URL__c": "https://github.com/${{ github.repository }}/commit/${{ github.sha }}"
      }'

2. Create Deployment Record

Endpoint:

POST {INSTANCE_URL}/services/data/v58.0/sobjects/Deployment__c

Headers:

Authorization: Bearer {ACCESS_TOKEN}
Content-Type: application/json

Payload:

{
  "Name": "ACME-1042 - a1b2c3d4",
  "Project_Key__c": "ACME-1042",
  "Deployed_At__c": "2025-11-17T14:30:00Z",
  "Git_SHA__c": "a1b2c3d4e5f6",
  "Git_Branch__c": "main",
  "Deployed_By__c": "github-user",
  "Status__c": "Success",
  "Environment__c": "Production",
  "Repository__c": "blackboxprogramming/blackroad-ACME-1042-backend",
  "Commit_URL__c": "https://github.com/blackboxprogramming/blackroad-ACME-1042-backend/commit/a1b2c3d4e5f6",
  "Duration_Seconds__c": 120
}

Response (201 Created):

{
  "id": "a0Y5e000000ABC1EAO",
  "success": true,
  "errors": []
}

GitHub Actions Implementation:

- name: Create Salesforce Deployment record
  run: |
    curl -X POST \
      "$SF_INSTANCE_URL/services/data/v58.0/sobjects/Deployment__c" \
      -H "Authorization: Bearer $SF_ACCESS_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "Name": "'"$PROJECT_KEY"' - '"${GITHUB_SHA:0:8}"'",
        "Project_Key__c": "'"$PROJECT_KEY"'",
        "Deployed_At__c": "'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'",
        "Git_SHA__c": "${{ github.sha }}",
        "Git_Branch__c": "${{ github.ref_name }}",
        "Deployed_By__c": "${{ github.actor }}",
        "Status__c": "Success",
        "Environment__c": "Production",
        "Repository__c": "${{ github.repository }}",
        "Commit_URL__c": "https://github.com/${{ github.repository }}/commit/${{ github.sha }}"
      }'

3. Query Salesforce (Optional)

Use Case: Get Project metadata before deploying

Endpoint:

GET {INSTANCE_URL}/services/data/v58.0/query?q=SELECT+Id,Name,Package_Type__c+FROM+Project__c+WHERE+Project_Key__c='ACME-1042'

Headers:

Authorization: Bearer {ACCESS_TOKEN}

Response:

{
  "totalSize": 1,
  "done": true,
  "records": [
    {
      "Id": "a0X5e000000XYZ1EAO",
      "Name": "Acme Corp - ACME-1042",
      "Package_Type__c": "OS"
    }
  ]
}

Required Salesforce Objects

Deployment Custom Object

API Name: Deployment__c Label: Deployment Record Name: Deployment Name (Auto Number: DEP-{0000})

Fields:

Field API Name Type Length Description
Project__c Lookup(Project__c) N/A Related project
Project_Key__c Text (External ID) 20 For upsert operations
Deployed_At__c DateTime N/A When deployment occurred
Git_SHA__c Text 40 Git commit SHA
Git_Branch__c Text 100 Git branch name
Deployed_By__c Text 100 GitHub username
Status__c Picklist N/A Success, Failed, Rollback, In Progress
Environment__c Picklist N/A Staging, Production
Repository__c Text 255 Full repo name (org/repo)
Commit_URL__c URL 255 Link to commit
Duration_Seconds__c Number(6,0) N/A Deploy duration
Error_Message__c Long Text Area 32768 If deploy failed

Complete GitHub Actions Workflow

File: .github/workflows/notify-salesforce.yml

name: Notify Salesforce After Deploy

on:
  workflow_run:
    workflows: ["Deploy to Production"]
    types:
      - completed

jobs:
  notify-salesforce:
    runs-on: ubuntu-latest
    if: ${{ github.event.workflow_run.conclusion == 'success' }}

    steps:
      - name: Extract Project Key from repo name
        id: project-key
        run: |
          REPO_NAME="${{ github.repository }}"
          PROJECT_KEY=$(echo "$REPO_NAME" | sed -n 's/.*blackroad-\([A-Z0-9-]*\)-.*/\1/p')

          if [ -z "$PROJECT_KEY" ]; then
            echo "Warning: Could not extract project key"
            PROJECT_KEY="UNKNOWN"
          fi

          echo "PROJECT_KEY=$PROJECT_KEY" >> $GITHUB_OUTPUT

      - name: Authenticate with Salesforce
        id: sf-auth
        env:
          SF_CLIENT_ID: ${{ secrets.SALESFORCE_CLIENT_ID }}
          SF_CLIENT_SECRET: ${{ secrets.SALESFORCE_CLIENT_SECRET }}
          SF_USERNAME: ${{ secrets.SALESFORCE_USERNAME }}
          SF_PASSWORD: ${{ secrets.SALESFORCE_PASSWORD }}
          SF_SECURITY_TOKEN: ${{ secrets.SALESFORCE_SECURITY_TOKEN }}
        run: |
          RESPONSE=$(curl -X POST "https://login.salesforce.com/services/oauth2/token" \
            -d "grant_type=password" \
            -d "client_id=$SF_CLIENT_ID" \
            -d "client_secret=$SF_CLIENT_SECRET" \
            -d "username=$SF_USERNAME" \
            -d "password=$SF_PASSWORD$SF_SECURITY_TOKEN")

          ACCESS_TOKEN=$(echo $RESPONSE | jq -r '.access_token')
          INSTANCE_URL=$(echo $RESPONSE | jq -r '.instance_url')

          echo "ACCESS_TOKEN=$ACCESS_TOKEN" >> $GITHUB_OUTPUT
          echo "INSTANCE_URL=$INSTANCE_URL" >> $GITHUB_OUTPUT

      - name: Update Salesforce Project record
        if: steps.project-key.outputs.PROJECT_KEY != 'UNKNOWN'
        env:
          SF_INSTANCE_URL: ${{ steps.sf-auth.outputs.INSTANCE_URL }}
          SF_ACCESS_TOKEN: ${{ steps.sf-auth.outputs.ACCESS_TOKEN }}
          PROJECT_KEY: ${{ steps.project-key.outputs.PROJECT_KEY }}
        run: |
          curl -X PATCH \
            "$SF_INSTANCE_URL/services/data/v58.0/sobjects/Project__c/Project_Key__c/$PROJECT_KEY" \
            -H "Authorization: Bearer $SF_ACCESS_TOKEN" \
            -H "Content-Type: application/json" \
            -d '{
              "Last_Deploy_At__c": "'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'",
              "Last_Deploy_SHA__c": "${{ github.sha }}",
              "Last_Deploy_Branch__c": "${{ github.ref_name }}",
              "Last_Deploy_Actor__c": "${{ github.actor }}",
              "Deploy_Status__c": "Success",
              "Environment__c": "Production",
              "Release_Notes_URL__c": "https://github.com/${{ github.repository }}/commit/${{ github.sha }}"
            }' || echo "Warning: Failed to update Project record"

      - name: Create Deployment record
        if: steps.project-key.outputs.PROJECT_KEY != 'UNKNOWN'
        env:
          SF_INSTANCE_URL: ${{ steps.sf-auth.outputs.INSTANCE_URL }}
          SF_ACCESS_TOKEN: ${{ steps.sf-auth.outputs.ACCESS_TOKEN }}
          PROJECT_KEY: ${{ steps.project-key.outputs.PROJECT_KEY }}
        run: |
          curl -X POST \
            "$SF_INSTANCE_URL/services/data/v58.0/sobjects/Deployment__c" \
            -H "Authorization: Bearer $SF_ACCESS_TOKEN" \
            -H "Content-Type: application/json" \
            -d '{
              "Name": "'"$PROJECT_KEY"' - '"${GITHUB_SHA:0:8}"'",
              "Project_Key__c": "'"$PROJECT_KEY"'",
              "Deployed_At__c": "'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'",
              "Git_SHA__c": "${{ github.sha }}",
              "Git_Branch__c": "${{ github.ref_name }}",
              "Deployed_By__c": "${{ github.actor }}",
              "Status__c": "Success",
              "Environment__c": "Production",
              "Repository__c": "${{ github.repository }}",
              "Commit_URL__c": "https://github.com/${{ github.repository }}/commit/${{ github.sha }}"
            }' || echo "Warning: Failed to create Deployment record"

Error Handling

Common Errors

Status Code Error Cause Solution
401 Unauthorized Token expired Re-authenticate
403 Forbidden Insufficient permissions Check user permissions in Salesforce
404 Not Found Project_Key__c doesn't exist Verify project key extraction logic
400 Bad Request Invalid field value Check datetime format (must be ISO 8601)

Retry Logic

- name: Update Salesforce (with retries)
  uses: nick-invision/retry@v2
  with:
    timeout_minutes: 2
    max_attempts: 3
    command: |
      curl -X PATCH ... (Salesforce API call)

Testing

Manual Test (curl)

# 1. Get access token
ACCESS_TOKEN=$(curl -X POST "https://login.salesforce.com/services/oauth2/token" \
  -d "grant_type=password" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "username=YOUR_USERNAME" \
  -d "password=YOUR_PASSWORD_AND_TOKEN" | jq -r '.access_token')

# 2. Update Project
curl -X PATCH \
  "https://your-domain.my.salesforce.com/services/data/v58.0/sobjects/Project__c/Project_Key__c/TEST-1234" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "Last_Deploy_At__c": "2025-11-17T14:30:00Z",
    "Deploy_Status__c": "Success"
  }'

GitHub Actions Test

  1. Push commit to test repo
  2. Trigger deploy workflow
  3. Verify Salesforce Project updated
  4. Check Deployment record created
  5. Review GitHub Actions logs for errors

Monitoring

Track These Metrics:

Metric Target Alert Threshold
Salesforce API Success Rate > 98% < 95%
Avg API Response Time < 1s > 3s
Failed Updates < 2% > 5%

Salesforce Report: "Deployments by Status (Last 30 Days)"


Security Best Practices

  1. Use Service Account:

    • Create integration-user@blackroad.com
    • Assign minimal permissions
    • Enable API-only user (no login UI)
  2. Rotate Credentials:

    • Rotate passwords every 90 days
    • Use GitHub encrypted secrets
    • Never commit credentials to git
  3. IP Restrictions:

  4. Audit Logging:

    • Enable Salesforce Event Monitoring
    • Log all API calls
    • Review monthly


Changelog

Date Version Change Author
2025-11-17 1.0 Initial specification Cece (Claude)