Brand key required

Webhooks

Events are POSTed to your endpoint as they happen, signed with HMAC-SHA256. Poll nothing.

Subscribe

POST/v1/webhooks

FieldTypeDescription
urlstringRequired. Must be an https:// URL.
eventsstring[]Required, at least one event from the table below. Unknown event names are rejected with 422.

request

curl -X POST "https://api.ugcroster.com/v1/webhooks" \
  -H "Authorization: Bearer rsk_brand_key" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://yourapp.com/hooks/roster", "events": ["deliverable.submitted", "commission.created"] }'

response 200

{
  "data": {
    "id": "wh_new001",
    "url": "https://yourapp.com/hooks/roster",
    "events": ["deliverable.submitted", "commission.created"],
    "secret": "whsec_1f6c…9e03",
    "active": true,
    "created_at": "2026-06-13T16:50:00Z"
  }
}

The full signing secret is returned only on creation. Store it immediately. The list endpoint shows a truncated version. Each brand can hold up to 10 subscriptions.

Events

EventTypeDescription
application.receivedcreatorA creator applied to one of your campaigns.
application.approvedbrandAn application was approved.
commission.createdsystemA new commission event was attributed to a creator.
commission.approvedbrandA commission was approved for payout.
payout.createdbrandA payout was recorded.
content.refreshedsystemTracked-post metrics were refreshed.
message.receivedcreatorA creator sent you a message.
deliverable.submittedcreatorA creator uploaded content against a deliverable.
deliverable.approvedbrandA submission was approved in review.
deliverable.revision_requestedbrandA submission was sent back with notes.
deliverable.declinedcreatorA creator declined an assigned deliverable.
deliverable.cancelledbrandA deliverable was cancelled before completion.

Manage subscriptions

GET/v1/webhooks

response 200

{
  "data": [
    {
      "id": "wh_001",
      "url": "https://yourapp.com/hooks/roster",
      "events": ["deliverable.submitted", "commission.created"],
      "active": true,
      "secret": "whsec_1f…9e03",
      "created_at": "2026-05-01T10:00:00Z",
      "last_triggered_at": "2026-06-13T12:00:00Z",
      "last_status_code": 200
    }
  ],
  "pagination": { "page": 1, "limit": 20, "total": 2, "has_more": false }
}

last_triggered_at and last_status_code tell you whether your endpoint is receiving and acknowledging deliveries.

DELETE/v1/webhooks/{id}

request

curl -X DELETE "https://api.ugcroster.com/v1/webhooks/wh_001" \
  -H "Authorization: Bearer rsk_brand_key"

Delivery format

POST to your endpoint

Headers:
  X-Roster-Signature: sha256=<hex HMAC-SHA256 of the raw body>

Body:
{ "event": "deliverable.submitted", "createdAt": "2026-09-05T21:04:11.000Z", "data": { … } }

Verify the signature

Every delivery is signed with your webhook secret. Compute the HMAC over the raw request body, before any JSON parsing: re-serialising the parsed object can change key order and break the comparison.

node.js

import crypto from 'crypto';

function verify(rawBody, signatureHeader, secret) {
  const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(signatureHeader), Buffer.from(expected));
}

python

import hmac
import hashlib

def verify(raw_body: bytes, signature_header: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode(), raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature_header, expected)

# In your handler:
is_valid = verify(request.body, request.headers["X-Roster-Signature"], "whsec_your_secret")

Always use a constant-time comparison (timingSafeEqual / compare_digest) and reject anything that fails verification before touching the payload.

Timeouts and retries

RuleTypeDescription
timeout5 secondsEach delivery attempt must get a response within 5 seconds.
retryonce, after 2sOn a network error or a 5xx response, the delivery is retried once after 2 seconds. 4xx responses are not retried.
observabilityper subscriptionThe most recent outcome is exposed as last_status_code on GET /v1/webhooks.

The budget is tight by design: respond 2xx immediately and process the event asynchronously. If your endpoint misses both attempts the event is not redelivered. Treat webhooks as a trigger to fetch current state from the API, not as the source of truth.