Documentation
Outgoing integration guide

Webhooks

Send selected repository, pull-request, pipeline, work, and package events to your HTTPS service, then verify and process every delivery without trusting the network.

Project scopedHMAC-SHA-256One explicit attempt
Start hereProject settings › Integrations › Webhooks
PermissionProject-scoped Manage projects and a fresh sign-in
Successful resultA Succeeded delivery with a verified signature and idempotent receiver record
01 / Before you begin

Own both ends of the delivery.

Prerequisites

  • A verified Mozaic account and membership in the organization.
  • An existing project. Webhooks created in the current workspace are project scoped.
  • The Manage projects claim at the project or organization scope.
  • A fresh sign-in to create, update, rotate, deactivate, or manually redeliver.
  • A publicly resolvable HTTPS endpoint that accepts POST requests and can preserve the exact request body for signature verification.

The webhook is governed in the project whose events it exports; the project-scoped Manage projects claim controls this workflow.

02 / Receiver preparation

Build the endpoint before creating the webhook.

  1. Expose one HTTPS route.Use a dedicated path such as /hooks/mozaic with a certificate trusted by normal public HTTPS clients.
  2. Read the raw body once.Signature verification covers the exact bytes sent. Do not parse, reformat, transcode, or reconstruct JSON before computing the HMAC.
  3. Persist an idempotency key.Use event_id as a unique processing key. A manual redelivery creates a new delivery but carries the same event.
  4. Acknowledge quickly.Verify, durably enqueue or record the event, and return a 2xx response. Perform slow integration work after acknowledgement.
  5. Keep a safe diagnostic trail.Record the delivery ID, event ID, event type, result, and processing time without logging the signing secret or sensitive payload unnecessarily.
Public destination required

The dispatcher does not reach loopback, local, private, link-local, carrier-grade NAT, documentation, or other reserved addresses. Use a public ingress that applies your own authentication and routing controls.

03 / Endpoint setup

Create a project webhook.

Organization›Projects›project›Project settings›Integrations›Webhooks
  1. Choose Endpoint.From an empty state the equivalent action is Create project endpoint. Enter a recognizable name, such as Release index or Deployment events.
  2. Enter the destination.Use an HTTPS URL on a public DNS hostname. URLs containing credentials, fragments, IP literals, localhost, or private destinations are rejected.
  3. Select events.Choose at least one exact event type from the supported list. Send only the data the receiver needs.
  4. Create the endpoint.Mozaic returns a signing secret beginning with mwhsec_.
  5. Copy the secret immediately.Store it in the receiver's secret manager. Mozaic derives the signing value when dispatching and does not expose it again.

Success state

The webhook appears as Active with its selected filters. The next matching project event creates an Initial delivery and POSTs one signed JSON envelope to the destination.

Names are limited to 120 characters. Destination URLs are limited to 2,048 characters. Updating an endpoint changes future deliveries only; existing delivery records retain their original evidence.

Repository and package moves change the project scope used for delivery, diagnostics, and manual redelivery. Disable affected source-project webhooks before moving, wait for admitted outgoing attempts to finish, and explicitly review destination endpoints. Reenabling an old endpoint does not restore access to moved events. Event envelopes keep their original project evidence; historical pipeline events keep the run's execution project. A retained event identifier or payload project is not current delivery authority.

04 / Event selection

Subscribe to exact, versioned event names.

AreaSupported event types
Repositoryrepository.created, repository.imported, repository.push, repository.archived, repository.reactivated, repository.exported, repository.deleted, repository.published
Pull requestpull_request.opened, pull_request.updated, pull_request.merged
Pipelinepipeline.started, pipeline.succeeded, pipeline.failed
Work itemwork_item.created, work_item.updated, work_item.transitioned
Packagepackage.published, package.symbols_published, package.unlisted, package.relisted, package.deprecation_changed, package.purged

Filters are an allowlist. A new product event is not sent to an existing endpoint unless its exact type is selected. Treat the envelope's schema_version and event_type as protocol values, and ignore additive JSON properties your version does not use.

05 / HTTP contract

Use headers for delivery context and JSON for domain data.

Request headers

HeaderMeaning
Content-Typeapplication/json.
User-AgentMozaic-Webhook/1.
X-Mozaic-Webhook-IdThe configured endpoint UUID.
X-Mozaic-Delivery-IdThe UUID for this exact attempt record.
X-Mozaic-EventThe lowercase event type, matching the envelope.
X-Mozaic-Delivery-TimestampThe dispatch time in UTC RFC 3339 form, to whole seconds.
X-Mozaic-Signature-256sha256= followed by the lowercase hexadecimal HMAC-SHA-256 of the exact body.

Version-one event envelope

Identity
event_id, organization_id, placement_generation, event_type, and schema_version.
Subject and actor
resource, actor, and visibility preserve the organization-bound subject and originator.
Ordering and tracing
aggregate_sequence, correlation_id, optional causation_id, and occurred_at.
Event data
payload is an event-specific JSON object, bounded to 64 KiB.

Aggregate sequence helps order events for one resource; it is not a global ordering guarantee. Correlation and causation values connect related operations but do not replace receiver idempotency.

06 / Signature verification

Authenticate the exact bytes before parsing.

The signing key is the complete show-once mwhsec_... string encoded as UTF-8. Compute HMAC-SHA-256 over the raw body, prefix the lowercase hex digest with sha256=, and compare in constant time.

C# / ASP.NET Coreminimal receiver fragment
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;

app.MapPost("/hooks/mozaic", async (HttpRequest request, IConfiguration config) =>
{
    using var buffer = new MemoryStream();
    await request.Body.CopyToAsync(buffer);
    var body = buffer.ToArray();

    var secret = config["Mozaic:WebhookSecret"];
    var supplied = request.Headers["X-Mozaic-Signature-256"].ToString();
    var timestampText = request.Headers["X-Mozaic-Delivery-Timestamp"].ToString();
    if (string.IsNullOrEmpty(secret) ||
        !DateTimeOffset.TryParse(timestampText, CultureInfo.InvariantCulture,
            DateTimeStyles.AssumeUniversal, out var timestamp) ||
        Math.Abs((DateTimeOffset.UtcNow - timestamp).TotalMinutes) > 5)
    {
        return Results.Unauthorized();
    }

    using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
    var expectedText = "sha256=" +
        Convert.ToHexString(hmac.ComputeHash(body)).ToLowerInvariant();
    var expected = Encoding.ASCII.GetBytes(expectedText);
    var actual = Encoding.ASCII.GetBytes(supplied);
    if (expected.Length != actual.Length ||
        !CryptographicOperations.FixedTimeEquals(expected, actual))
    {
        return Results.Unauthorized();
    }

    using var envelope = JsonDocument.Parse(body);
    var eventId = envelope.RootElement.GetProperty("event_id").GetGuid();
    // Insert eventId with a unique constraint and enqueue work atomically.
    return Results.NoContent();
});
  1. Reject missing context.Require the signature, timestamp, delivery ID, and event header.
  2. Bound replay.Parse the delivery timestamp and accept a small clock-skew window appropriate to your ingress. The example uses five minutes.
  3. Compute the HMAC.Use the full secret and exact body bytes.
  4. Compare in constant time.Reject different lengths and invalid digests before parsing or acting on the event.
  5. Validate the envelope.Require schema version 1, a known event type, and an expected project or organization binding before dispatching application logic.
Framework body handling matters

Some middleware consumes or normalizes the request body before a route handler runs. Enable raw-body buffering at the ingress boundary and verify against those original bytes.

07 / Reliable processing

Make every handler idempotent.

  1. Start a local transaction.Insert event_id into a receipt table with a unique constraint and enqueue or record the intended work in the same transaction.
  2. Treat duplicates as success.If the event ID already exists, return 2xx without repeating the side effect. A manual redelivery is expected to reach this path.
  3. Return before slow work.The entire HTTP attempt has a 15-second timeout. A durable queue or outbox lets your business action continue independently.
  4. Handle each event version explicitly.Route on both event_type and schema_version; log and safely acknowledge or quarantine unsupported versions according to your policy.
  5. Use resource sequence carefully.For state projections, retain the highest accepted aggregate_sequence per resource and reconcile gaps from Mozaic instead of assuming global delivery order.

Success state

The receiver verifies the delivery, durably records the event exactly once, returns a 2xx response within the timeout, and processes the downstream action independently.

08 / Delivery evidence

Inspect the exact attempt before redelivering.

Project settings›Integrations›Webhooks›select endpoint›Delivery history
Pending
The delivery is recorded and waiting to be leased by the dispatcher.
In flight
One dispatcher owns the active HTTP attempt.
Succeeded
The receiver returned any 2xx status.
Failed
The dispatcher completed the attempt and received a non-2xx response or rejected the destination.
Unknown
The request or response did not complete with enough evidence to know whether the receiver acted, such as a transport failure or interrupted response read.

Mozaic makes one automatic attempt. It does not automatically retry Failed or Unknown deliveries. The record includes event type, kind, status, timing, HTTP status when known, a bounded error, and up to 4,096 bytes of response excerpt.

Manual redelivery

  1. Open the terminal delivery.Inspect its status, response, receiver logs, and event ID.
  2. Fix or confirm the receiver.For Unknown, first check whether the original event was already recorded.
  3. Choose Redeliver.Fresh authentication and Manage projects are required.
  4. Follow the new record.Mozaic creates a distinct Manual redelivery linked to the original; it never rewrites the Initial attempt.
Unknown is not safe to repeat blindly

The receiver may have completed the side effect before the connection failed. Deduplicate by event ID, then redeliver only after the endpoint can return a deterministic 2xx response.

09 / Secret and endpoint lifecycle

Rotate without leaving stale trust behind.

Rotate a signing secret

  1. Open the webhook settings.Confirm the endpoint identity and coordinate a short receiver change window.
  2. Choose Rotate secret.Fresh authentication is required. Copy the new mwhsec_... value from the show-once response.
  3. Update the receiver immediately.The previous secret becomes invalid as soon as rotation succeeds; there is no dual-secret overlap.
  4. Verify the next delivery.Confirm the endpoint accepts the new signature and no secret appears in logs.

Deactivate an endpoint

Deactivate the webhook to stop future matching events from creating deliveries. Existing endpoint metadata and delivery history remain available as evidence. Reactivate only after the destination and secret configuration are ready again.

10 / Limits and security

Design inside the outbound boundary.

BoundaryCurrent behavior
TransportHTTPS only; direct connection with no proxy and no redirects.
DestinationPublic DNS hostname only. Credentials, fragments, IP literals, localhost, private, local, multicast, and reserved ranges are rejected.
DNSRevalidated at delivery time and pinned to a permitted public address for that request.
Connect timeout5 seconds.
Total request timeout15 seconds.
SuccessAny HTTP 2xx response.
Automatic attemptsOne; retry is an explicit manual redelivery.
Event payloadJSON object, 64 KiB maximum inside the version-one envelope.
Stored diagnosticsResponse excerpt up to 4,096 bytes; error text up to 1,000 characters.
Collection page sizeUp to 100 endpoints or deliveries per page.
  • Treat the signing secret like a password. Store it in a secret manager and grant the receiver process read access only.
  • Signature verification authenticates content; it does not make every event appropriate for every downstream action. Enforce your own resource and event allowlists.
  • Return generic, bounded error bodies. Response excerpts are retained in Mozaic and should not contain credentials or sensitive internal diagnostics.
  • Expect additive payload fields. Fail closed on unsupported schema versions, but avoid breaking on fields your handler does not consume.
Inbound API automation

Call scoped APIs with a session-bound human token.

Create a personal access token in the target tenant workspace under Settings › Access › API tokens. The token is bound to that one organization and its human owner. Choose only Manage projects when the integration must list or create projects, and only Manage work when it must manage project work. Choose Provision source to list, create, or import repositories and list or register named pipeline definitions. This scope does not issue Git credentials, execute pipelines, delete configuration, or publish packages.

Issue PATs from a personal-account organization session with fresh local MFA. Company/provider-only sessions cannot issue PATs. Select Issue API token to review the exact organization, expiry and requested scopes. Confirming that review opens the required MFA prompt automatically; complete the passkey or authenticator challenge to continue issuance. Cancelling the review or challenge does not issue a token. Each scope shows its claim name separately from its description.

A PAT retains human purpose and its exact issuing tenant session. Every request must satisfy the current action policy and session evidence; logout, session rotation or loss of required proof can stop access before token expiry. A PAT never receives a machine-policy exception. Repository or package credentials delegated through it retain human provenance and their own scopes, expiry and revocation controls. Previously issued PATs without session evidence cannot satisfy actions that require that evidence. Use an explicitly supported machine-purpose credential for unattended Git or package access where the organization policy permits it.

Integrations can link to the target organization workspace at /settings/access/api-tokens?organization=<organization-uuid>&scopes=manage_source,run_pipelines,manage_packages&name=Migration&expires_in_days=1. Use the workspace's own tenant origin and immutable organization ID. The link preselects a request for the user to inspect and approve; it never issues a token automatically. Only the four shown query parameters are accepted. Requested scopes must be supported, and the organization must match the workspace. Duplicate parameters, callbacks, redirect destinations and token parameters are rejected. Never put credentials or confidential data in the URL. The issued token appears only once after approval and must be transferred through a protected channel.

Selected scopeSupported version-one API operations
Provision sourceGET and POST /api/v1/organizations/{organization_id}/projects/{project_id}/repositories; POST /api/v1/organizations/{organization_id}/projects/{project_id}/repositories:import (multipart bundle upload); GET and POST /api/v1/organizations/{organization_id}/repositories/{repository_id}/pipelines.
Provision packagesPOST /api/v1/organizations/{organization_id}/projects/{project_id}/package-registries (OCI only); GET and POST /api/v1/organizations/{organization_id}/package-registries/{registry_id}/repositories; POST /api/v1/organizations/{organization_id}/package-registries/{registry_id}/credentials (explicit-repository OCI pull only, expiry capped to PAT).
Provision source + Authorize pipeline triggersPOST /api/v1/organizations/{organization_id}/repositories/{repository_id}/credentials issues a repository-scoped Git credential capped to PAT expiry. Both delegations and current source/run authority are required. Use the credential for a real governed Git update; manual runs retain restricted trust.
Manage projectsGET and POST /api/v1/organizations/{organization_id}/projects.
Manage work
  • GET and POST /api/v1/organizations/{organization_id}/projects/{project_id}/work-items
  • GET and POST /api/v1/organizations/{organization_id}/projects/{project_id}/milestones
  • GET and POST /api/v1/organizations/{organization_id}/projects/{project_id}/boards
  • GET /api/v1/organizations/{organization_id}/work-items/{work_item_id}
  • PUT /api/v1/organizations/{organization_id}/work-items/{work_item_id}/assignment
  • GET and POST /api/v1/organizations/{organization_id}/work-items/{work_item_id}/relations
  1. Keep the target explicit.Use the matching tenant hostname and verify the organization ID before any mutation. A token for one organization is never accepted for another.
  2. Send the bearer in one header.Use Authorization: Bearer <token>. Do not place the value in a URL, source file, payload, work item, log, status file, or command transcript.
  3. Use protocol controls.Supply Idempotency-Key where the endpoint supports it, and use the current ETag with If-Match for version-checked assignment or relation changes.
  4. Reconcile uncertainty.If a non-idempotent Work mutation loses its response, read current server state before deciding whether to repeat it. Never infer that a timeout means the mutation did not commit.
  5. Close one-off access.Verify counts and relationships, then revoke the token with fresh MFA. Expiry, revocation, issuing-session logout or rotation, membership removal, action-policy changes and live role changes take effect without waiting for the token lifetime.
Bearer and browser protections are different.

An API bearer is non-cookie authentication, so it does not use CSRF. The tenant web interface still requires its secure session cookie, exact-origin and fetch-metadata checks, and a CSRF token. A bearer cannot authenticate Git, LFS, NuGet, operator, account, realtime, or any unlisted API route.

11 / Troubleshooting

Resolve common integration failures.

The destination is rejected during setup

Use an https:// URL with a public DNS hostname and no embedded username, password, fragment, or IP literal. Local tunnel and documentation-range addresses are intentionally blocked.

Signature verification always fails

Use the complete mwhsec_... value as UTF-8 HMAC key, hash the exact raw request bytes, emit lowercase hexadecimal with the sha256= prefix, and compare in constant time. Check whether middleware changed or consumed the body.

The delivery is Failed with a redirect status

Mozaic does not follow redirects. Configure the final HTTPS destination directly and make the route return 2xx after durable acceptance.

The delivery is Unknown

The dispatcher cannot prove whether the receiver acted. Look up event_id in the receiver's idempotency store before choosing Redeliver; never infer failure solely from Unknown.

The endpoint times out

Respond within 15 seconds. Move downstream API calls, builds, or data synchronization behind a durable local queue and acknowledge only after the event has been recorded safely.

No delivery appears for an expected action

Confirm the endpoint is Active, the exact event type is selected, and the action occurred in this endpoint's project. Existing filters do not automatically include newly introduced event types.

Deliveries fail immediately after secret rotation

Rotation invalidates the old secret immediately. Install the new show-once value in the receiver, then manually redeliver affected terminal records after idempotency checks.

An API bearer returns 401 or 403

Verify the tenant hostname, token expiry, selected scope, supported route, and the owner's current organization membership and effective role. Revoke and replace a suspected token through the browser; never send the raw bearer to an operator for diagnosis.