Webhooks & Automation

Automate your build workflow with webhooks. Receive notifications when builds complete, or trigger builds automatically on code changes.

Incoming Webhooks (Trigger Builds)

GitHub Push Trigger

Automatically build when you push to a branch.

Configuration

  1. Go to Creator Portal → Project → Settings → Webhooks
  2. Enable Auto-build on push
  3. Configure filters:
{
  "trigger": "push",
  "enabled": true,
  "branches": ["main", "release/*"],
  "paths": {
    "include": ["Source/**", "CMakeLists.txt"],
    "exclude": ["docs/**", "*.md"]
  },
  "build_config": {
    "platforms": ["macos", "windows"],
    "formats": ["vst3", "au"],
    "release_type": "dev"
  }
}

Trigger Rules

FieldDescription
branchesOnly trigger for these branches
paths.includeOnly trigger when these files change
paths.excludeNever trigger for these files

Example: Production Releases

{
  "trigger": "push",
  "branches": ["main"],
  "build_config": {
    "release_type": "patch",
    "flags": {
      "enableActivationKeys": true
    }
  }
}

GitHub Release Trigger

Build when you create a GitHub release.

{
  "trigger": "release",
  "enabled": true,
  "release_types": ["published"],
  "build_config": {
    "release_type": "release",
    "platforms": ["macos", "windows"],
    "formats": ["vst3", "au"]
  }
}

Manual Webhook Trigger

Trigger builds from your own CI/CD.

Endpoint:

POST https://api.beatconnect.com/functions/v1/external-builds/{project_id}/trigger

Authentication:

X-Webhook-Secret: your-webhook-secret

Or HMAC signature:

X-Signature: sha256=abc123...

Request Body:

{
  "ref": "refs/heads/main",
  "commit_sha": "abc1234...",
  "platforms": ["macos", "windows"],
  "formats": ["vst3", "au"],
  "release_type": "patch"
}

Response:

{
  "build_id": "build-uuid",
  "status": "queued"
}

Outgoing Webhooks (Notifications)

Receive notifications when build status changes.

Configuration

  1. Go to Creator Portal → Project → Settings → Webhooks
  2. Add Notification webhook URL
  3. Select events to receive

Events

EventDescription
build.queuedBuild created and waiting
build.startedBuild execution started
build.successBuild completed successfully
build.failedBuild failed
build.cancelledBuild was cancelled

Payload Format

{
  "event": "build.success",
  "timestamp": "2025-01-15T10:35:00Z",
  "build": {
    "id": "build-uuid",
    "project_id": "project-uuid",
    "version": "1.0.1",
    "build_number": 5,
    "status": "success",
    "platforms": ["macos", "windows"],
    "formats": ["vst3", "au"],
    "duration_seconds": 245,
    "artifacts": [
      {
        "platform": "macos",
        "format": "vst3",
        "file_name": "MyPlugin.vst3",
        "file_size": 5242880,
        "download_url": "https://..."
      }
    ]
  }
}

Failure Payload

{
  "event": "build.failed",
  "timestamp": "2025-01-15T10:35:00Z",
  "build": {
    "id": "build-uuid",
    "status": "failed",
    "error_message": "CMake configuration failed",
    "error_logs": "CMake Error at CMakeLists.txt:15...",
    "failed_at_step": "configure"
  }
}

Signature Verification

All outgoing webhooks include a signature:

X-BeatConnect-Signature: sha256=abc123...
X-BeatConnect-Timestamp: 1642320000

Verify in Node.js:

const crypto = require('crypto');
 
function verifyWebhook(payload, signature, timestamp, secret) {
  const signedPayload = `${timestamp}.${payload}`;
  const expectedSignature = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');
 
  // Timing-safe comparison
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

Verify in Python:

import hmac
import hashlib
 
def verify_webhook(payload, signature, timestamp, secret):
    signed_payload = f"{timestamp}.{payload}"
    expected = 'sha256=' + hmac.new(
        secret.encode(),
        signed_payload.encode(),
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

Integration Examples

Slack Notifications

// Webhook receiver
app.post('/webhook/beatconnect', async (req, res) => {
  const { event, build } = req.body;
 
  let message;
  let color;
 
  switch (event) {
    case 'build.success':
      message = `✅ Build ${build.version} succeeded!`;
      color = 'good';
      break;
    case 'build.failed':
      message = `❌ Build ${build.version} failed: ${build.error_message}`;
      color = 'danger';
      break;
    default:
      message = `Build ${build.version}: ${event}`;
      color = 'warning';
  }
 
  await fetch(process.env.SLACK_WEBHOOK_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      attachments: [{
        color,
        text: message,
        fields: [
          { title: 'Version', value: build.version, short: true },
          { title: 'Build #', value: build.build_number, short: true }
        ]
      }]
    })
  });
 
  res.status(200).send('OK');
});

Discord Notifications

app.post('/webhook/beatconnect', async (req, res) => {
  const { event, build } = req.body;
 
  const embed = {
    title: event === 'build.success'
      ? '✅ Build Succeeded'
      : '❌ Build Failed',
    color: event === 'build.success' ? 0x00ff00 : 0xff0000,
    fields: [
      { name: 'Version', value: build.version, inline: true },
      { name: 'Platforms', value: build.platforms.join(', '), inline: true }
    ],
    timestamp: new Date().toISOString()
  };
 
  if (event === 'build.failed') {
    embed.fields.push({
      name: 'Error',
      value: build.error_message.slice(0, 1000)
    });
  }
 
  await fetch(process.env.DISCORD_WEBHOOK_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ embeds: [embed] })
  });
 
  res.status(200).send('OK');
});

GitHub Actions Integration

Trigger BeatConnect build from GitHub Actions:

# .github/workflows/build.yml
name: Build Plugin
 
on:
  push:
    branches: [main]
    paths:
      - 'Source/**'
      - 'CMakeLists.txt'
 
jobs:
  trigger-build:
    runs-on: ubuntu-latest
    steps:
      - name: Trigger BeatConnect Build
        run: |
          curl -X POST \
            "https://api.beatconnect.com/functions/v1/external-builds/${{ secrets.BEATCONNECT_PROJECT_ID }}/trigger" \
            -H "X-Webhook-Secret: ${{ secrets.BEATCONNECT_WEBHOOK_SECRET }}" \
            -H "Content-Type: application/json" \
            -d '{
              "ref": "${{ github.ref }}",
              "commit_sha": "${{ github.sha }}",
              "platforms": ["macos", "windows"],
              "formats": ["vst3", "au"]
            }'
 
      - name: Wait for Build
        run: |
          # Poll for build completion (simplified)
          sleep 300  # Wait 5 minutes
 
      - name: Download Artifacts
        run: |
          # Download signed binaries for further processing
          curl -o artifacts.zip \
            "https://api.beatconnect.com/functions/v1/builds/$BUILD_ID/download/all" \
            -H "Authorization: Bearer ${{ secrets.BEATCONNECT_TOKEN }}"

Auto-Deploy to Website

// After build success, deploy to your download page
app.post('/webhook/beatconnect', async (req, res) => {
  const { event, build } = req.body;
 
  if (event !== 'build.success') {
    return res.status(200).send('OK');
  }
 
  // Update your website's download links
  await updateDownloadPage({
    version: build.version,
    artifacts: build.artifacts.map(a => ({
      platform: a.platform,
      format: a.format,
      url: a.download_url
    }))
  });
 
  // Notify via email
  await sendEmail({
    to: 'team@yourcompany.com',
    subject: `New Release: v${build.version}`,
    body: `Version ${build.version} is ready for download.`
  });
 
  res.status(200).send('OK');
});

Webhook Security

Best Practices

  1. Always verify signatures — Never trust unverified payloads
  2. Check timestamps — Reject old webhooks (replay protection)
  3. Use HTTPS — Never use HTTP for webhook endpoints
  4. Rotate secrets — Change webhook secrets periodically

Timestamp Validation

function isTimestampValid(timestamp, toleranceSeconds = 300) {
  const now = Math.floor(Date.now() / 1000);
  return Math.abs(now - parseInt(timestamp)) <= toleranceSeconds;
}

IP Allowlisting

BeatConnect webhooks come from these IPs:

  • Contact support for current IP ranges
  • Use signature verification as primary security

Troubleshooting

Webhook Not Received

  1. Check endpoint URL is correct
  2. Verify endpoint returns 2xx status
  3. Check firewall allows incoming connections
  4. Review webhook logs in Creator Portal

Signature Mismatch

  1. Ensure you’re using the raw request body
  2. Check secret matches exactly
  3. Verify timestamp is included in signature

Duplicate Webhooks

Implement idempotency:

const processedBuilds = new Set();
 
app.post('/webhook', (req, res) => {
  const buildId = req.body.build.id;
 
  if (processedBuilds.has(buildId)) {
    return res.status(200).send('Already processed');
  }
 
  processedBuilds.add(buildId);
  // Process webhook...
});

Rate Limits

DirectionLimit
Incoming (triggers)10/minute per project
Outgoing (notifications)Unlimited

Next Steps