Documentation Index
Fetch the complete documentation index at: https://allridegmbh.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
Overview
JobTicket+ delivers webhook events as HTTPPOST requests to your registered endpoint. Each delivery includes:
- A JSON body containing the event metadata and resource payload
- An
X-Signing-Signatureheader you must verify to confirm the request genuinely came from JobTicket+
Event Types
employee-subscription-changed
Fired whenever an employee’s subscription status or key dates change (e.g. activation, scheduled pause, cancellation).
Payload
Fields
| Field | Type | Description |
|---|---|---|
event.id | string (UUID) | Unique ID of this event |
event.type | string | Always employee-subscription-changed |
event.timestamp | integer | Unix timestamp (seconds) when the event was generated |
payload.id | integer | Employee ID |
payload.resource | string | Always employee |
payload.subscription.status | string | New subscription status |
payload.subscription.startAt | string | null | ISO 8601 date the subscription starts |
payload.subscription.pausedAt | string | null | ISO 8601 date the subscription will be / was paused |
payload.subscription.cancelledAt | string | null | ISO 8601 date the subscription was cancelled |
payload.subscription.nextTicketAt | string | null | ISO 8601 date the next ticket will be issued |
payload.subscription.reactivatedAt | string | null | ISO 8601 date the subscription was reactivated |
new-employee-document-generated
Fired when a new document has been generated and is ready for an employee.
Payload
Fields
| Field | Type | Description |
|---|---|---|
event.id | string (UUID) | Unique ID of this event |
event.type | string | Always new-employee-document-generated |
event.timestamp | integer | Unix timestamp (seconds) when the event was generated |
payload.id | integer | Document ID — use with Get Employee Document |
payload.employeeId | integer | ID of the employee the document belongs to |
payload.resource-type | string | Always employee-document |
Verifying Signatures
JobTicket+ signs every webhook delivery so you can prove the request came from JobTicket+ and has not been tampered with. You should reject any request that fails verification.How the signature is built
When JobTicket+ sends a webhook it:- Takes the raw JSON body as a string (byte-for-byte, before any parsing).
- Takes the current Unix timestamp in seconds (call it
t). - Concatenates them with a dot:
<rawBody>.<t>. - Computes HMAC-SHA256 of that string using your first signing secret →
s1. - Computes HMAC-SHA256 of that same string using your second signing secret →
s2. - Puts everything in the
X-Signing-Signaturerequest header:
<rawBody>.<t> — the literal request body string, a dot, and the timestamp integer.
How you verify it
Capture the raw request body
Read the raw bytes of the incoming request body before JSON-parsing it. Any difference in whitespace or encoding will break the HMAC comparison. Most frameworks provide a raw-body hook — use it.
Reject stale events (replay protection)
Compare
t to your current time. If the event is older than 5 minutes, reject it — this prevents an attacker from capturing a valid request and replaying it later.Re-compute the expected signatures
Using the same formula JobTicket+ used, compute the HMAC for each of your two signing secrets:
Accept if either secret matches
Compare the values you computed against If at least one matches, the request is authentic. If neither matches, reject it with
s1 and s2 from the header using a constant-time comparison (to prevent timing attacks).400.Accepting either secret is intentional — it lets you rotate one secret at a time without dropping valid deliveries. See Zero-downtime rotation below.
Code examples
Worked example
Given:- Raw body:
{"event":{"id":"abc","type":"employee-subscription-changed","timestamp":1778662082},"payload":{"id":4443}} - Timestamp (
t):1778662083 - Secret 1:
my-first-secret
HMAC-SHA256(key="my-first-secret", message=<above>) produces s1. JobTicket+ puts that value in the header alongside t and s2.
When your server receives the request, it rebuilds the same message from its own raw body + the t from the header, runs the same HMAC, and compares. If the body was altered in transit — even by a single space — the HMAC will not match.
Zero-downtime Secret Rotation
Each webhook has two signing secrets. JobTicket+ always includes boths1 and s2 in every delivery, and your handler should accept a request if either one matches. This design lets you rotate one secret without dropping any deliveries:
Rotate the first secret
In the portal, open the ⋯ menu for the webhook and click Rotate First
Webhook Signing Secret. Your handler currently accepts either the old or
new first secret (since
s2 is unchanged and still matches).Deploy the new secret to your server
Update your environment variables / secret manager with the new first secret
value. Your handler now matches on the new
s1.Responding to Webhooks
Return any2xx HTTP status within 10 seconds to acknowledge receipt.
Security Checklist
Read the raw request body before JSON parsing
Parse
t, s1, and s2 from the X-Signing-Signature headerReject the request if
|now - t| > 300 seconds (replay protection)Re-compute
HMAC-SHA256("<rawBody>.<t>") for each of your two secretsUse a constant-time comparison — never a plain string equality check
Accept the request if either computed HMAC matches
s1 or s2Return
2xx immediately; process the payload asynchronously
