CrewPass pushes a webhook to your registered endpoint when data about a crew member on your fleet changes — so you don’t have to poll. Delivery is asynchronous, signed, retried with backoff, and dead-lettered after repeated failure. You receive an event only for a crew member on your fleet, only for an event whose scope you hold, and only when that crew member’s consent allows it — the same gate as the read surface.

How it works

CrewPass continuously watches the data behind your fleet. When something relevant changes — a certificate finishes verifying, compliance flips, a status or profile updates — CrewPass builds the matching event and delivers it to the callback URL you registered. There is nothing to run on your side beyond an HTTPS endpoint that accepts a POST, verifies the signature, and returns 2xx. Two things worth knowing:
  • You don’t poll. Events are pushed in near-real-time off CrewPass’s own change feed; you react to them.
  • Delivery is at-least-once. The same event may arrive more than once (e.g. after a retry), so deduplicate on event_id.

Subscribing

During onboarding, CrewPass registers your callback URL, the events you want, and issues a webhook signing secret used to sign every delivery (this is separate from the request-signing secret used by the v2 write surface; v1 reads are not signed at all).

The envelope

Every delivery is a JSON body with this shape:
{
  "schema_version": 1,
  "event_id": "evt_abc",
  "event_type": "crew.document.processed",
  "occurred_at": "2026-06-08T10:00:00Z",
  "partner_id": "prt_123",
  "data": { }
}
And these headers:
HeaderValue
X-CrewPass-Event-IdThe event id (dedupe on this).
X-CrewPass-Event-TypeThe event type.
X-CrewPass-TimestampUnix seconds at signing time.
X-CrewPass-Signaturev1=<hex(hmac_sha256(secret, "{timestamp}." + raw_body))>
X-CrewPass-Schema-Version1

Outbound signing differs from request signing

The outbound webhook signature signs "{timestamp}.{body}" — there is no nonce. This is deliberately different from inbound request signing (used by the v2 write surface, not by v1 reads), which signs "{timestamp}.{nonce}.{body}". Use the right scheme for the direction.

Verifying a delivery

Python
import hashlib, hmac

def verify(secret: str, timestamp: str, signature: str, raw_body: bytes) -> bool:
    expected = "v1=" + hmac.new(
        secret.encode(), f"{timestamp}.".encode() + raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

# In your handler: read the RAW body bytes (do not re-serialize), then:
# verify(SECRET, request.headers["X-CrewPass-Timestamp"],
#        request.headers["X-CrewPass-Signature"], raw_body)
Node
import crypto from "node:crypto";

export function verify(secret, timestamp, signature, rawBody) {
  const expected =
    "v1=" +
    crypto.createHmac("sha256", secret).update(`${timestamp}.` + rawBody).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}

Receiver requirements

  1. Verify the signature against the raw bytes.
  2. Deduplicate on event_id — delivery is at-least-once, so the same event may arrive more than once.
  3. Respond 2xx quickly and process asynchronously.
  4. Anything else is retried with exponential backoff (up to 8 attempts), then dead-lettered. A 4xx (other than 408/429) is treated as a permanent rejection and not retried.

Event catalogue (v1)

EventFires whenScope required
crew.document.processedA certificate finishes processing & verificationcrew:documents:read
crew.document.updatedA document’s verification status changescrew:documents:read
crew.compliance.changedA crew member’s compliance state changescrew:compliance:read
crew.status.changedVerification or background-check status changescrew:status:subscribe
crew.profile.updatedA crew member updates their profilecrew:profile:read

crew.document.processed / crew.document.updated

{
  "crew_unique_id": "crew_001",
  "document_id": "doc_9",
  "type": "ENG1",
  "category": "Medical Certificate",
  "title": "ENG1 Medical",
  "issuer": "Approved Authority",
  "verification_status": "verified",
  "issue_date": "2026-06-01",
  "expiry_date": "2028-06-01",
  "document_number": "E1-998"
}

crew.compliance.changed

{
  "crew_unique_id": "crew_001",
  "vessel_id": "ves_abc",
  "overall_status": "at_risk",
  "requirements_met": 7,
  "requirements_total": 8,
  "requirements_expiring": 1,
  "next_expiry_date": "2026-08-01"
}

crew.status.changed

{
  "crew_unique_id": "crew_001",
  "verification_status": "verified",
  "background_check_status": "completed"
}

crew.profile.updated

{ "crew_unique_id": "crew_001", "updated_at": "2026-06-08T12:00:00Z" }
The verification provider behind a background check or certificate is never named in any payload — only the standardised, CrewPass-owned status vocabulary.