Webhooks
Webhooks send HTTP POST notifications when your builds, submissions, or OTA updates complete or fail. Norrix supports three webhook types:
| Type | Description |
|---|---|
| Generic | JSON payload to any HTTPS endpoint, signed with HMAC-SHA256 |
| Slack | Rich Block Kit messages delivered to a Slack channel |
| Teams | Adaptive Card notifications via Power Automate (recommended) or legacy MessageCard |
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 a Webhook
- Go to Dashboard → Settings → Webhooks
- Click + Add Webhook
- Enter a Name (e.g., “Build Notifications”)
- Select a Webhook Type (Generic, Slack, or Teams)
- Enter the URL for your chosen type (see setup guides below)
- Select Events to subscribe to
- Optionally configure Project Filtering
- Click Create
For Generic webhooks, a signing secret is displayed after creation. Copy it immediately — it won’t be shown again.
For Slack and Teams webhooks, no signing secret is generated. The webhook URL itself serves as authentication.
Slack Setup
Slack webhooks deliver rich, formatted messages using Block Kit with status indicators and metadata fields.
Creating a Slack Incoming Webhook
- Go to api.slack.com/apps
- Click Create New App → From scratch
- Name the app (e.g., “Norrix Builds”) and select your workspace
- Go to Incoming Webhooks → Toggle Activate Incoming Webhooks on
- Click Add New Webhook to Workspace
- Select the channel to post to and click Allow
- Copy the webhook URL (starts with
https://hooks.slack.com/services/...)
Adding to Norrix
- In Dashboard → Settings → Webhooks, click + Add Webhook
- Select Slack as the type
- Paste the Slack webhook URL
- Select events and click Create
What You’ll See in Slack
Notifications include:
- Header with status (checkmark for success, X for failure)
- Fields with project name, platform, version, build number, configuration, and distribution type
- Optional message when a build, submit, or update includes a message
- Context footer with organization name and operation ID
Microsoft Teams Setup
Teams webhooks deliver Adaptive Card notifications through Power Automate, or legacy MessageCard notifications if your tenant still allows Incoming Webhooks.
Option A: Power Automate (Recommended)
- Go to Power Automate and create an Instant cloud flow
- Choose the When an HTTP request is received trigger
- Save the flow to generate the HTTP POST URL
- Add the Post adaptive card in a chat or channel action
- Pick the Team and Channel
- For Adaptive Card, use the dynamic content
cardfrom the trigger body - Save the flow and copy the HTTP POST URL
Option B: Legacy Incoming Webhook (If Enabled)
- Open Microsoft Teams and navigate to the channel you want notifications in
- Click the … menu on the channel → Connectors (or Manage channel → Connectors)
- Find Incoming Webhook and click Configure
- Name the webhook (e.g., “Norrix Builds”) and optionally upload an icon
- Click Create
- Copy the webhook URL (from
outlook.office.comoroutlook.office365.com)
Adding to Norrix
- In Dashboard → Settings → Webhooks, click + Add Webhook
- Select Teams as the type
- Paste the Power Automate URL (recommended) or the legacy Incoming Webhook URL
- Select events and click Create
Payload for Power Automate
The Teams adapter sends a JSON body that includes the full Norrix payload plus an Adaptive Card:
{
"event": "build_completed",
"timestamp": "2024-01-15T10:30:00.000Z",
"organization": { "id": "org_abc123", "name": "My Team" },
"data": { "type": "build" },
"text": "Build Completed: my-app",
"card": { "type": "AdaptiveCard", "version": "1.5", "body": [] }
}In your flow, pass card to the Post adaptive card in a chat or channel action.
What You’ll See in Teams
Notifications include:
- Adaptive Card with status color (green for success, red for failure)
- Title with status emoji and operation type
- Facts section with build type, configuration, distribution, fingerprint, and organization
- Optional message when a build, submit, or update includes a message
Generic Webhook Setup
Generic webhooks send the full JSON payload to any HTTPS endpoint. Payloads are signed with HMAC-SHA256 so your server can verify authenticity.
Adding to Norrix
- In Dashboard → Settings → Webhooks, click + Add Webhook
- Select Generic as the type
- Enter your HTTPS endpoint URL
- Select events and click Create
- Copy the signing secret — it’s only shown once
Payload Format
{
"event": "build_completed",
"timestamp": "2024-01-15T10:30:00.000Z",
"organization": { "id": "org_abc123", "name": "My Team" },
"data": {
"type": "build",
"buildId": "build-1234567890",
"projectName": "my-app",
"appId": "com.example.myapp",
"platform": "ios",
"version": "1.0.0",
"buildNumber": "42",
"buildType": "release",
"configuration": "prod",
"distributionType": "appstore",
"status": "completed",
"artifactUrl": "https://...",
"fingerprintHash": "abc123..."
}
}Headers
| Header | Description |
|---|---|
Content-Type | application/json |
User-Agent | Norrix-Webhook/1.0 |
X-Norrix-Event | Event name (e.g., build_completed) |
X-Norrix-Delivery | Unique delivery ID |
X-Norrix-Timestamp | ISO 8601 timestamp |
X-Norrix-Signature | sha256=<hex> HMAC-SHA256 signature |
Payload Fields
Build Events
| Field | Type | Description |
|---|---|---|
buildId | string | Build ID |
projectName | string | Project name |
appId | string | App bundle identifier |
platform | string | ios, android, or visionos |
version | string | App version (e.g., 1.2.0) |
buildNumber | string | Build number |
buildType | string | debug or release |
configuration | string | Deployment target (e.g., prod, stg) |
distributionType | string | appstore, adhoc, or enterprise |
status | string | completed or failed |
artifactUrl | string | Download URL for IPA/APK |
fingerprintHash | string | Native dependency fingerprint |
message | string | Optional status message |
Submit Events
| Field | Type | Description |
|---|---|---|
submitId | string | Submission ID |
appId | string | App bundle identifier |
platform | string | ios, android, or visionos |
version | string | App version |
track | string | Store track (e.g., production, testflight) |
buildId | string | Associated build ID |
status | string | completed or failed |
message | string | Optional status message |
Update Events (OTA)
| Field | Type | Description |
|---|---|---|
updateId | string | Update ID |
appId | string | App bundle identifier |
platform | string | ios, android, or visionos |
version | string | App version |
buildNumber | string | Build number |
configuration | string | Deployment target |
status | string | completed or failed |
updateUrl | string | URL to the update bundle |
fingerprintHash | string | Native dependency fingerprint |
message | string | Optional status message |
Signature Verification
Verify the X-Norrix-Signature header using HMAC-SHA256 with your signing secret:
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 (with type badge for Slack/Teams)
- URL
- Events
- Enabled status
- Last triggered
- Last status code
Edit Webhook
- Click on the webhook
- Update name, URL, events, or project filters
- Save changes
The webhook type cannot be changed after creation. To switch types, delete and recreate the webhook.
Regenerate Secret
Available for Generic webhooks only (Slack and Teams don’t use signing secrets):
- Edit the webhook
- Click Regenerate Secret
- Copy the 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 the event ID for deduplication:
async function handleEvent(event) {
if (await isProcessed(event.data.id)) {
return; // Already handled
}
// Process event
await markProcessed(event.data.id);
}Security
- Generic: Always verify HMAC-SHA256 signatures. Rotate secrets if compromised.
- Slack: Keep your webhook URL private. Regenerate in Slack if compromised.
- Teams: Keep your webhook URL private. Regenerate the connector if compromised.
- All types require HTTPS endpoints.