Webhooks let your own application receive a near-real-time notification whenever certain things happen on your Assignr site — a game gets published, an official is changed on a game, a user is added to your site, or an official is attached to a league. Instead of polling the API for changes, Assignr sends an HTTP request to a URL you control as soon as the event happens.
There's no single in-app "Webhooks" page that does everything — setting it up involves two places in the app plus one API call, described below.
If you don't see the settings described below, this feature may not be turned on for your site yet, contact Assignr support.
This feature is currently in private beta. If you wish to be considered for access, please contact Assignr support.
Before you start
You'll need:
- A site administrator to turn on the specific events you want to send (Step 1 below).
- An OAuth application with access to the
webhooksscope (Step 2 below). - A publicly reachable URL on your own server that can receive an HTTPS POST request and
respond quickly (Assignr does not wait around for a slow endpoint).
Step 1: Choose which events can be sent
Before any event will actually go out, it has to be turned on for your site (and, for
league-scoped events, for the specific league).
Site-wide events:
- Go to Site Settings and open the Webhooks panel.
- In Webhooks Enabled, select the events you want to allow:
- Game Published
- Game Official Changed
- User Created
- Save.
League-scoped events:
- Open the league and go to its settings.
- In the same Webhooks Enabled field, select from:
- Game Published
- Game Official Changed
- Official Attached to a League
- Save.
Only the events checked here are eligible to fire at all — even if your API subscription (Step 3)
asks for an event type, Assignr won't send it unless it's also enabled here.
Step 2: Get API access with the webhooks scope
Webhook subscriptions are created and managed through the Assignr API, so you need an OAuth
application first. OAuth Applications are created by the Assignr support team, you'll need to get in touch to set one up.
Step 3: Create a subscription
There's no in-app form for the actual subscription — you create it with an API call, using the
access token from Step 2.
POST https://api.assignr.com/api/v2/webhooks/subscriptions
| Field | Required | Description |
|---|---|---|
site_id | Yes | The site this subscription belongs to. |
event_types | Yes | One or more event type strings (see Event reference). |
target_url | Yes | The URL Assignr will POST to when a matching event fires. |
target_method | Yes | The HTTP method to use — use POST. |
target_headers | No | An object of custom headers to include on every delivery (e.g. an API key your endpoint expects). |
enabled | No | Defaults to true. Set to false to pause deliveries without deleting the subscription. |
Example request body:
{
"site_id": 12345,
"event_types": ["game.game.published", "game.official.changed"],
"target_url": "https://example.com/webhooks/assignr",
"target_method": "POST",
"target_headers": { "X-My-App-Key": "abc123" }
}
A successful response (201) returns the subscription, including a generated secret you'll use
to verify deliveries:
{
"id": 987,
"site_id": 12345,
"event_types": ["game.game.published", "game.official.changed"],
"enabled": true,
"secret": "bd957e7b-c79c-496b-8ba7-d023c31be30e",
"target_method": "POST",
"target_url": "https://example.com/webhooks/assignr",
"target_headers": { "X-My-App-Key": "abc123" },
"created": "2026-08-24T12:00:00Z"
}
Save the secret when you create the subscription — there's no separate screen to look it up
later other than fetching the subscription again via the API (see below).
Managing an existing subscription
All of these also require the webhooks scope:
| Action | Request |
|---|---|
| List your subscriptions | GET /api/v2/webhooks/subscriptions |
| View one subscription | GET /api/v2/webhooks/subscriptions/:id |
| Update a subscription | PUT /api/v2/webhooks/subscriptions/:id (send only the fields you want to change) |
| Delete a subscription | DELETE /api/v2/webhooks/subscriptions/:id |
Pause deliveries temporarily with PUT .../subscriptions/:id and {"enabled": false}, rather than
deleting and recreating the subscription.
What you'll receive
Each delivery is a small JSON "pointer" — it tells you what happened and where to look, rather
than including the full record. Fetch the linked resource (with your own API access token) to get
full details.
Example delivery for game.game.published:
{
"id": 55501,
"topic": "game.game.published",
"_links": {
"self": { "resource-type": "webhook-event", "href": "https://api.assignr.com/api/v2/webhooks/events/55501.json" },
"resource": { "resource-type": "game", "href": "https://api.assignr.com/api/v2/games/778899.json" },
"owner": { "resource-type": "site", "href": "https://api.assignr.com/api/v2/sites/12345.json" },
"game": { "resource-type": "game", "href": "https://api.assignr.com/api/v2/games/778899.json" }
}
}
For game.official.changed, the resource link points at the affected assignment, and a
separate game link is included so you don't have to look up the assignment first to find its
game.
Verifying a delivery is genuinely from Assignr
Each delivery includes a signature header so you can confirm it wasn't spoofed:
X-Hook0-Signature: t=<unix_timestamp>,h=x-event-id x-event-type,v1=<hmac_sha256_hex>
To verify:
- Build the signed string:
{timestamp}.x-event-id x-event-type.{event id}.{event topic}.{raw request body}. - Compute an HMAC-SHA256 of that string, keyed with your subscription's
secret. - Compare it to the
v1value in the header (constant-time comparison recommended). - Reject requests whose timestamp is too old, to guard against replay.
The request also includes X-Event-Id, X-Event-Type, Content-Type: application/json, and any
custom target_headers you configured on the subscription.
Sample Verification Code
require "openssl"
require "json"
module Assignr
class WebhookVerificationError < StandardError; end
module_function
# raw_body: the exact, unparsed request body (do not re-serialize parsed JSON)
# signature_header: the value of the X-Hook0-Signature header
# secret: the subscription's secret, from the API response when you created it
def verify_webhook!(raw_body:, signature_header:, secret:, tolerance: 300)
fields = signature_header.to_s.split(",").each_with_object({}) do |part, memo|
key, value = part.split("=", 2)
memo[key] = value
end
timestamp, header_list, signature = fields.values_at("t", "h", "v1")
if timestamp.nil? || header_list.nil? || signature.nil?
raise WebhookVerificationError, "Malformed signature header"
end
if (Time.now.to_i - timestamp.to_i).abs > tolerance
raise WebhookVerificationError, "Timestamp outside of tolerance"
end
event = JSON.parse(raw_body)
signed_payload = [timestamp, header_list, event["id"], event["topic"], raw_body].join(".")
expected_signature = OpenSSL::HMAC.hexdigest("SHA256", secret, signed_payload)
unless secure_compare(expected_signature, signature)
raise WebhookVerificationError, "Signature mismatch"
end
event
end
def secure_compare(a, b)
a.bytesize == b.bytesize && OpenSSL.fixed_length_secure_compare(a, b)
end
end
import hashlib
import hmac
import json
import time
class WebhookVerificationError(Exception):
pass
def verify_webhook(raw_body: bytes, signature_header: str, secret: str, tolerance: int = 300) -> dict:
"""
raw_body: the exact, unparsed request body bytes (don't re-serialize parsed JSON)
signature_header: the value of the X-Hook0-Signature header
secret: the subscription's secret, from the API response when you created it
"""
fields = dict(part.split("=", 1) for part in signature_header.split(","))
timestamp = fields.get("t")
header_list = fields.get("h")
signature = fields.get("v1")
if not timestamp or not header_list or not signature:
raise WebhookVerificationError("Malformed signature header")
if abs(int(time.time()) - int(timestamp)) > tolerance:
raise WebhookVerificationError("Timestamp outside of tolerance")
event = json.loads(raw_body)
signed_payload = ".".join([
timestamp,
header_list,
str(event["id"]),
event["topic"],
raw_body.decode("utf-8"),
])
expected_signature = hmac.new(
secret.encode("utf-8"), signed_payload.encode("utf-8"), hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected_signature, signature):
raise WebhookVerificationError("Signature mismatch")
return event
Event reference
| Event type | Label in Site/League settings | Fires when |
|---|---|---|
game.game.published | Game Published | A game is published, if the game is public. |
game.official.changed | Game Official Changed | An official is added, removed, or replaced on a public game that hasn't happened yet. |
site.user.created | User Created | A user is added to your site. Site-level only. |
site.league_official.created | Official Attached to a League | An official is attached to a league. League-level only. |
Limitations
- There's currently no delivery log in the app — no list of past deliveries, response codes, or a
redelivery button. If you suspect deliveries are failing, double-check your endpoint is
reachable and returning a fast response, or contact Assignr support. target_methodisn't restricted in the API to a specific list of values, but POST is the
supported and expected method — other values aren't guaranteed to work.
