Code Examples

Ready-to-use webhook implementations for popular backend frameworks and CI/CD integration patterns.

Get started quickly with these webhook implementation examples. All examples include proper security validation and error handling.

Backend Framework Examples

webhook.js
app.use('/webhook', express.raw({type: 'application/json'}));

function validateSignature(payload, signature, secret) {
  const expectedSignature = 'sha256=' +
    crypto.createHmac('sha256', secret)
          .update(payload)
          .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

app.post('/webhook', async (req, res) => {
  const signature = req.headers['x-statused-signature'];
  const secret = process.env.WEBHOOK_SECRET;

  if (!validateSignature(req.body, signature, secret)) {
    return res.status(401).send('Unauthorized');
  }

  const event = JSON.parse(req.body);
  const { type, data } = event;

  switch (type) {
    case 'com.statused.app.status.updated':
      await handleAppStatusChange(data);
      break;
    case 'com.statused.build.status.updated':
      await handleBuildStatusChange(data);
      break;
  }

  res.status(200).send('OK');
});

async function handleAppStatusChange(data) {
  switch (data.status) {
    case 'Ready for Sale':
      await notifyTeam(`🎉 ${data.app_name} v${data.version} is live!`);
      break;
    case 'In Review':
      await toggleFeatureFlags(data.app_name, false);
      break;
    case 'Rejected':
      await createIncident(data);
      break;
  }
}
webhooks_controller.rb
class WebhooksController < ApplicationController
  skip_before_action :verify_authenticity_token
  before_action :validate_signature

  def statused
    event = JSON.parse(request.raw_post)

    case event['type']
    when 'com.statused.app.status.updated'
      handle_app_status_change(event['data'])
    when 'com.statused.build.status.updated'
      handle_build_status_change(event['data'])
    end

    head :ok
  end

  private

  def validate_signature
    signature = request.headers['X-Statused-Signature']
    secret = Rails.application.credentials.webhook_secret
    body = request.raw_post

    expected_signature = 'sha256=' + OpenSSL::HMAC.hexdigest('SHA256', secret, body)

    unless ActiveSupport::SecurityUtils.secure_compare(signature, expected_signature)
      head :unauthorized and return
    end
  end

  def handle_app_status_change(data)
    case data['status']
    when 'Ready for Sale'
      NotificationService.notify_team("🎉 #{data['app_name']} is live!")
    when 'In Review'
      FeatureFlagService.toggle(data['app_name'], enabled: false)
    when 'Rejected'
      IncidentService.create_from_rejection(data)
    end
  end
end
routes.rb
post '/webhooks/statused', to: 'webhooks#statused'
views.py
@csrf_exempt
@require_http_methods(["POST"])
def statused_webhook(request):
    signature = request.META.get('HTTP_X_STATUSED_SIGNATURE')

    if not validate_signature(request.body, signature):
        return HttpResponseBadRequest("Invalid signature")

    event = json.loads(request.body)
    event_type = event.get('type')
    data = event.get('data', {})

    if event_type == 'com.statused.app.status.updated':
        handle_app_status_change(data)
    elif event_type == 'com.statused.build.status.updated':
        handle_build_status_change(data)

    return HttpResponse("OK")

def validate_signature(payload, signature):
    if not signature:
        return False

    secret = settings.WEBHOOK_SECRET
    expected_signature = 'sha256=' + hmac.new(
        secret.encode(),
        payload,
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(signature, expected_signature)

def handle_app_status_change(data):
    status = data.get('status')

    if status == 'Ready for Sale':
        notify_release.delay(data.get('app_name'))
    elif status == 'In Review':
        toggle_features.delay(data.get('app_name'), False)
    elif status == 'Rejected':
        create_incident.delay(data)
urls.py
path('webhooks/statused/', views.statused_webhook, name='statused_webhook'),

CI/CD System Integration

Most CI/CD systems don’t natively accept generic webhooks from external services like Statused. However, you can integrate Statused with popular mobile-focused CI/CD platforms using their APIs or plugin systems:

PlatformNative Webhook SupportIntegration MethodDocumentation
Bitrise❌ NoUse Bitrise REST API to trigger builds (requires API token)Triggering Builds via API – Bitrise Docs
GitHub Actions❌ NoExternal trigger via Repository Dispatch event (GitHub REST API)Repository Dispatch API – GitHub Docs
Jenkins✅ Yes (with plugin)Direct webhook trigger using a plugin (e.g. Generic Webhook Trigger) or via intermediate serviceGeneric Webhook Trigger Plugin – Jenkins
GitLab CI✅ YesBuilt-in Pipeline Triggers (trigger token & API call)Pipeline Triggers – GitLab Docs
Azure DevOps✅ YesIncoming Webhook triggers (YAML pipeline webhook resource)Webhook Resource Triggers – Azure DevOps Docs
CircleCI❌ NoTrigger via CircleCI API (e.g. Pipeline Trigger endpoint)Trigger Pipeline via API – CircleCI Docs

Each “No” above means you’ll need to set up a small backend (or use a serverless function) to receive the Statused webhook and then call the CI/CD’s API. For platforms marked “Yes”, you can configure them to accept incoming webhooks directly without an extra proxy in between.

For platforms without native webhook support, we recommend implementing a backend proxy:

  1. StatusedYour BackendCI/CD Platform API
  2. Your backend receives the webhook, validates it, and triggers the appropriate CI/CD action
  3. This gives you full control over authentication, filtering, error handling, and custom logic

Backend Proxy Example

webhook.js
async function handleBuildStatusChange(data) {
  if (data.status === 'Valid') {
    // Build is valid - trigger GitHub Actions to submit for review
    await fetch(`https://api.github.com/repos/${process.env.GITHUB_OWNER}/${process.env.GITHUB_REPO}/dispatches`, {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${process.env.GITHUB_TOKEN}`,
        "Accept": "application/vnd.github+json",
        "X-GitHub-Api-Version": "2022-11-28",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        event_type: "submit-for-review",
        client_payload: {
          app_name: data.app_name,
          build_version: data.build_version,
        }
      })
    });
  } else {
    // Build failed - notify team via Slack/Teams
    await notifyTeam(`⚠️ Build processing failed for ${data.app_name} v${data.build_version}. Please investigate.`);
  }
}
webhooks_controller.rb
class WebhooksController < ApplicationController
  skip_before_action :verify_authenticity_token

  # …

  def handle_build_status_change(data)
    if data['status'] == 'Valid'
      # Build is valid - trigger GitHub Actions to submit for review
      trigger_github_actions(data)
    else
      # Build failed - notify team via Slack/Teams
      NotificationService.notify_team("⚠️ Build processing failed for #{data['app_name']} v#{data['build_version']}. Please investigate.")
    end
  end

  private

  def trigger_github_actions(data)
    uri = URI("https://api.github.com/repos/#{ENV['GITHUB_OWNER']}/#{ENV['GITHUB_REPO']}/dispatches")
    req = Net::HTTP::Post.new(uri)
    req["Authorization"] = "Bearer #{ENV['GITHUB_TOKEN']}"
    req["Accept"] = "application/vnd.github+json"
    req["X-GitHub-Api-Version"] = "2022-11-28"
    req.content_type = "application/json"
    req.body = {
      event_type: "submit-for-review",
      client_payload: {
        app_name: data["app_name"],
        build_version: data["build_version"],
      }
    }.to_json
    Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
  end
end
views.py
def handle_build_status_change(data):
    if data.get('status') == 'Valid':
        # Build is valid - trigger GitHub Actions to submit for review
        trigger_github_actions(data)
    else:
        # Build failed - notify team via Slack/Teams
        notify_team.delay(f"⚠️ Build processing failed for {data.get('app_name')} v{data.get('build_version')}. Please investigate.")

def trigger_github_actions(data):
    url = f"https://api.github.com/repos/{settings.GITHUB_OWNER}/{settings.GITHUB_REPO}/dispatches"
    headers = {
        "Authorization": f"Bearer {settings.GITHUB_TOKEN}",
        "Accept": "application/vnd.github+json",
        "X-GitHub-Api-Version": "2022-11-28"
    }
    payload = {
        "event_type": "submit-for-review",
        "client_payload": {
            "app_name": data["app_name"],
            "build_version": data["build_version"],
        }
    }
    requests.post(url, json=payload, headers=headers, timeout=10)

GitHub Actions workflow

.github/workflows/submit-for-review-webhook.yml
name: Submit App for Review
on:
  repository_dispatch:
    types: [submit-for-review]

jobs:
  submit-for-review:
    runs-on: ubuntu-latest
    steps:
      - name: Submit app for review
        run: |
          echo "🚀 Submitting ${{ github.event.client_payload.app_name }} \
          v${{ github.event.client_payload.build_version }} for review..."
          # e.g. fastlane deliver --submit_for_review

Key references if you need further details or up-to-date API information:

Feature Flag Integration

You can use your backend to toggle feature flags based on app status changes. Check your feature flagging service documentation for their API or SDK usage.

Support

Want direct CI/CD integration?

Missing your CI/CD platform or want us to build a direct integration? Reach out to us — we prioritize new integrations based on user demand and can help you get set up quickly.

Need help with implementation?

These examples should get you started, but every integration is unique. If you need help adapting these examples to your specific use case, reach out to us — we're happy to help with your webhook implementation.