Webhooks
Webhooks send HTTP POST requests to your server when builds, submissions, or updates complete.
Events
| Event | Description |
|---|---|
build_completed | Build finished successfully |
build_failed | Build failed |
submit_completed | Store submission succeeded |
submit_failed | Store submission failed |
update_completed | OTA update published |
update_failed | OTA update failed |
all | Subscribe to all events |
Creating Webhooks
From Dashboard
- Go to Dashboard → Settings → Webhooks
- Click Create Webhook
- Enter:
- Name: Friendly name (e.g., “Slack Notifications”)
- URL: HTTPS endpoint (must be HTTPS)
- Events: Select events to subscribe to
- Project filter: Optional, comma-separated project names
- Click Create
- Copy the signing secret. It’s only shown once
Payload Format
Webhooks receive JSON payloads:
{
"event": "build_completed",
"timestamp": "2024-01-15T10:30:00.000Z",
"data": {
"id": "build-1234567890",
"projectName": "my-app",
"platform": "ios",
"status": "success",
"version": "1.0.0",
"buildNumber": "42",
"buildType": "release",
"configuration": "prod",
"distributionType": "appstore",
"organizationId": "org_abc123"
}
}Payload Fields
| Field | Type | Description |
|---|---|---|
id | string | Build, submit, or update ID |
projectName | string | Project name |
platform | string | Platform: ios, android, or visionos |
status | string | Result status: success or failed |
version | string | App version |
buildNumber | string | Build number |
buildType | string | Build type: debug or release |
configuration | string | Deployment target (e.g., prod, stg, dev) |
distributionType | string | iOS distribution: appstore, adhoc, enterprise |
organizationId | string | Organization ID |
Signature Verification
Webhooks include an HMAC-SHA256 signature for verification:
X-Norrix-Signature: sha256=<hex_signature>Verification Example (Node.js)
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expected =
'sha256=' +
crypto.createHmac('sha256', secret).update(payload, 'utf8').digest('hex');
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
// Express middleware
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-norrix-signature'];
const payload = req.body.toString();
if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(payload);
// Handle event...
res.status(200).send('OK');
});Project Filtering
Filter webhooks to specific projects:
Include Projects
Comma-separated list of project names to include:
my-app, other-appOnly events from these projects trigger the webhook.
Exclude Projects
Comma-separated list of project names to exclude:
test-app, experimentalEvents from these projects are skipped.
Managing Webhooks
View Webhooks
Dashboard → Settings → Webhooks shows:
- Name
- URL
- Events
- Enabled status
- Last triggered
- Last status code
Edit Webhook
- Click on the webhook
- Update fields
- Save changes
Regenerate Secret
- Edit the webhook
- Click Regenerate Secret
- Copy new secret
- Update your server
Disable/Enable
Toggle the Enabled switch to temporarily disable a webhook without deleting it.
Delete Webhook
- Click Delete
- Confirm deletion
Delivery history is also deleted.
Delivery History
View recent deliveries for each webhook:
- Timestamp
- Event type
- HTTP status code
- Response time
- Error message (if failed)
Retry Failed Deliveries
Currently, failed deliveries aren’t automatically retried. Configure your endpoint to be idempotent.
Best Practices
Respond Quickly
Return 200 OK within 10 seconds. Process events asynchronously:
app.post('/webhook', (req, res) => {
// Acknowledge receipt immediately
res.status(200).send('OK');
// Process asynchronously
processEventAsync(req.body);
});Idempotency
Events may be delivered multiple times. Use event ID for deduplication:
async function handleEvent(event) {
if (await isProcessed(event.data.id)) {
return; // Already handled
}
// Process event
await markProcessed(event.data.id);
}Error Handling
Log failures for debugging:
app.post('/webhook', async (req, res) => {
try {
await processEvent(req.body);
res.status(200).send('OK');
} catch (error) {
console.error('Webhook error:', error);
res.status(500).send('Error');
}
});Security
- Always verify signatures
- Use HTTPS endpoints
- Keep secrets secure
- Rotate secrets if compromised