Skip to main content

Generic Webhook Integration

For CI/CD systems other than GitHub Actions (Jenkins, GitLab CI, CircleCI, or a custom server), use a generic webhook. Myop sends a signed POST request to your endpoint whenever a component is released.

How It Works

Release component in dashboard


Myop sends POST to your URL ──► Your server / CI endpoint
(signed with HMAC-SHA256) │
Verify signature

npx myop export --releases '...'

Deploy to CDN

Setup

Step 1: Add a Webhook URL

  1. Go to Dashboard > Rollout > Settings (gear icon) > Self-Hosting
  2. Enable self-hosted sync
  3. Enter your endpoint URL and click Add

Add generic webhook

  1. Copy the signing secret — it's only shown once. You'll use this to verify that webhook requests are really from Myop.

Signing secret

  1. Click Save Configuration

Step 2: Build Your Webhook Receiver

Your endpoint receives a POST request with the following format:

Headers:

HeaderValue
Content-Typeapplication/json
X-Myop-Eventcomponents.deployed
X-Myop-Signaturesha256=<hex-encoded HMAC>

Body:

{
"event": "components.deployed",
"timestamp": "2026-03-24T13:37:00.000Z",
"organizationId": "org-abc-123",
"data": {
"releases": [
{
"componentId": "2098cfba-5124-48ff-b87d-6cd71c14cf7a",
"variantId": "57a5c9ef-8775-4a19-a243-4cadb87a3e6f",
"environment": "production",
"environmentId": "d6c518e0-f80b-4cbe-9efd-b51aa37f2324"
}
]
}
}

Step 3: Verify the Signature

Always verify the X-Myop-Signature header to ensure the request is from Myop. The signature is an HMAC-SHA256 hash of the raw request body using your signing secret.

Node.js / Express
const crypto = require('crypto');

function verifySignature(rawBody, signature, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');

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

app.post('/webhooks/myop', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-myop-signature'];

if (!verifySignature(req.body, signature, process.env.MYOP_WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}

const payload = JSON.parse(req.body);
// Trigger your sync pipeline...
res.sendStatus(200);
});
Python / Flask
import hmac
import hashlib

def verify_signature(body: bytes, signature: str, secret: str) -> bool:
expected = 'sha256=' + hmac.new(
secret.encode(), body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)

@app.route('/webhooks/myop', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Myop-Signature', '')
if not verify_signature(request.data, signature, WEBHOOK_SECRET):
return 'Invalid signature', 401

payload = request.json
# Trigger your sync pipeline...
return '', 200

Step 4: Run the Export

After verifying the signature, extract the releases array from the payload and pass it to the Myop CLI:

Incremental sync (from webhook payload)
npx myop export \
--api-key "$MYOP_API_KEY" \
--releases '[{"componentId":"2098cfba-...","environment":"production","environmentId":"d6c518e0-...","variantId":"57a5c9ef-..."}]'

The --releases flag tells the CLI to export only the specific component+environment that changed, making the sync fast.

For a full sync (e.g., on schedule or when data.fullSync is true):

Full sync
npx myop export --api-key "$MYOP_API_KEY"

Step 5: Deploy to CDN

Upload the exported files to your CDN:

aws s3 sync ./myop-static s3://your-bucket \
--delete \
--cache-control "public, max-age=300"

Complete Example: Express Server

A minimal webhook receiver that verifies signatures and triggers a sync:

webhook-server.js
const express = require('express');
const crypto = require('crypto');
const { execSync } = require('child_process');

const app = express();
const WEBHOOK_SECRET = process.env.MYOP_WEBHOOK_SECRET;
const MYOP_API_KEY = process.env.MYOP_API_KEY;

app.post('/webhooks/myop', express.raw({ type: 'application/json' }), (req, res) => {
// 1. Verify signature
const signature = req.headers['x-myop-signature'];
const expected = 'sha256=' + crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(req.body)
.digest('hex');

if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
console.error('Invalid webhook signature');
return res.status(401).send('Invalid signature');
}

// 2. Parse payload
const payload = JSON.parse(req.body);
const releases = payload.data?.releases;
console.log(`Received ${releases?.length || 0} release(s)`);

// 3. Run export
try {
if (releases && releases.length > 0) {
execSync(`npx myop export --releases '${JSON.stringify(releases)}'`, {
env: { ...process.env, MYOP_API_KEY },
stdio: 'inherit'
});
} else {
execSync('npx myop export', {
env: { ...process.env, MYOP_API_KEY },
stdio: 'inherit'
});
}

// 4. Deploy (replace with your CDN upload command)
execSync('aws s3 sync ./myop-static s3://your-bucket --delete', {
stdio: 'inherit'
});

res.sendStatus(200);
} catch (err) {
console.error('Sync failed:', err.message);
res.status(500).send('Sync failed');
}
});

app.listen(3000, () => console.log('Webhook receiver listening on port 3000'));

CI/CD Examples

GitLab CI

Use a GitLab webhook trigger to start a pipeline:

.gitlab-ci.yml
sync-myop:
stage: deploy
image: node:20
script:
- npx myop export
- aws s3 sync ./myop-static s3://$AWS_S3_BUCKET --delete
only:
- triggers

Set your webhook URL to https://gitlab.com/api/v4/projects/{id}/trigger/pipeline and pass the trigger token as a query parameter.

Jenkins

Configure a Generic Webhook Trigger plugin on your Jenkins job. Set the webhook URL to your Jenkins endpoint and extract $.data.releases from the payload.

Retry Policy

Webhooks are delivered with a 10-second timeout. If your endpoint doesn't respond in time, the delivery is marked as failed. Myop does not retry failed webhooks — use a scheduled full sync as a fallback to catch any missed deliveries.

Testing

Click Sync Now in the Self-Hosting settings to trigger a test delivery. Check the webhook status indicator to confirm your endpoint received it successfully.