Skip to content
Dashboard, webhooks & logs

Dashboard, webhooks & logs

Web Dashboard

The admin web dashboard provides remote management of claims and players through a browser interface:

web-dashboard:
  enabled: false
  # Interface to bind. Keep 127.0.0.1 and use a TLS reverse proxy; 0.0.0.0 exposes the admin API.
  bind-address: "127.0.0.1"
  port: 8095
  # Public URL shown in the /scs dashboard message. The login token is given separately.
  # Change this when the dashboard is behind a reverse proxy or a custom domain.
  url: "http://localhost:8095"
  # Token lifetime in hours. 0 or negative is clamped to the max (168h / 7 days).
  token-ttl-hours: 24
  # Allowed CORS origins. Fallback: localhost + 127.0.0.1 on the configured port.
  # "*" means "any origin": insecure, only for local trusted setups.
  cors-origins:
    - "http://localhost:8095"
    - "http://127.0.0.1:8095"

Key reference

KeyDescription
enabledStarts the embedded HTTP server on plugin load. A /scs reload now stops or (re)starts it to match this value. (2.7.3)
bind-addressNetwork interface the server binds to. Default 127.0.0.1 (localhost only). Set 0.0.0.0 to listen on all interfaces — only do this behind a TLS reverse proxy. (2.7.3)
portTCP port the dashboard listens on.
urlPublic URL shown in the in-game /scs dashboard link. Since 2.7.3 the token is no longer in the URL — it is sent as a separate copy-to-clipboard message to paste into the page. Change this when the dashboard is behind a reverse proxy or a custom domain.
token-ttl-hoursHow long an issued admin token stays valid, in hours. 0 or a negative value is clamped to the maximum (168h / 7 days) — tokens can no longer be made non-expiring. (2.7.3)
cors-originsWhitelist of Origin header values the dashboard will send CORS-allow headers for. Incoming requests whose Origin doesn't match are silently denied at the browser level.

Run /scs dashboard in-game. The plugin sends a clickable [Click here] link opening the dashboard URL, plus a separate login token message you click to copy and paste into the page's login field. The token is kept in the browser's sessionStorage for that tab. Keeping the token out of the URL avoids leaking it into browser history, proxy logs or a screen share. (2.7.3)

Tokens are stored hashed in web-tokens.json and persist across restarts. Run /scs dashboard revoke to invalidate all issued tokens at once (e.g. after a token leak or a staff change). (2.7.3)

Built-in abuse protection

  • Rate limiting — the /auth endpoint accepts at most 5 attempts per IP per minute, with a global per-IP limiter on every route (attempt counters are bounded so they can't be used to exhaust memory). Over the limit returns HTTP 429 with Retry-After. (2.7.3)
  • Request-size cap — request bodies over 64 KB are rejected (HTTP 413) and slow requests time out, so an unauthenticated client can't exhaust memory or threads. (2.7.3)
  • Per-request authorization — every request re-checks the holder's scs.admin.dashboard permission live, so revoking a staff member's access takes effect immediately. Since 2.8 every write action (deleting or editing claims, permissions, flags, members — all of /api/admin/* and every POST to /api/manage/*) additionally requires the holder to be online and still hold the permission; only read-only endpoints stay reachable offline, bounded by the token TTL. A revoked or offline staff token can no longer mutate anything. (2.7.3 / 2.8)
  • Security headers — responses carry a Content-Security-Policy, X-Frame-Options: DENY, X-Content-Type-Options: nosniff and a strict CORS policy (* is refused). Player-set text is HTML-escaped. (2.7.3)
  • Path traversal — static file requests are canonicalised; .. / ~ are rejected.
  • Prepared statements — all dashboard DB access goes through the same DAO layer, no raw SQL concatenation.

Because write actions require the token holder to be online since 2.8, an admin must be connected to the server (on any server sharing the same data) to create, edit or delete claims from the dashboard. Reading the dashboard (claims, players, stats, audit logs) still works while offline, until the token expires.

The dashboard uses plain HTTP (no built-in TLS). Since 2.7.3 it binds to 127.0.0.1 by default, so it is not reachable from the network out of the box. To access it remotely, set bind-address: 0.0.0.0 and put it behind a reverse proxy that terminates TLS — never expose the plain port to the internet.

On filesystems with multi-user access, consider restricting the web-tokens.json file (e.g., chmod 640). The tokens grant admin access to the dashboard.

Discord Webhook

SCS2 can post claim events (claims, unclaims, sales, purchases, ban/kick actions, /requnclaim submissions, etc.) to Discord. You can define multiple named webhooks and choose which event goes to which webhook.

discord-webhook:
  enabled: false
  url: ""                 # legacy single webhook — used as "default" if "webhooks" is empty

  webhooks:               # define one or more named webhooks
    default:
      url: "https://discord.com/api/webhooks/..."
      username: ""        # optional name override
      avatar-url: ""      # optional avatar override
  #  staff:
  #    url: "https://discord.com/api/webhooks/..."

  default-routes:         # events with no explicit route below go here
    - default

  routing:                # event -> webhook(s), overrides default-routes
  #  claim-deleted:
  #    - default
  #    - staff
  #  claim-restored:
  #    - staff
  #  unclaim-request:
  #    - staff

Set enabled: true and configure at least one webhook URL. See Integrations → Discord Webhooks for the full list of routable event names. Leaving webhooks empty falls back to the single url (which then receives every event), so older configs keep working unchanged. Run /scs reload to apply changes.

Per-event toggles (Added in 2.6.6)

The optional events block turns individual events on or off at the source. An event set to false is never sent to any webhook — this is independent of routing, which only chooses which webhook receives an event that is being sent. Every event you omit defaults to true, so existing configs keep posting everything.

discord-webhook:
  events:
    chunk-added: false        # stop the per-chunk spam...
    chunk-removed: false      # ...while keeping everything else
    member-added: true
    member-role-changed: true
    # any event not listed here defaults to true

The example above silences the noisy chunk-added / chunk-removed events while still posting member and role changes. See Integrations → Discord Webhooks for the full list of event names.

Audit Logs

Track every action performed on claims for accountability and moderation:

audit-logs:
  enabled: false
  retention-days: 30
  # Buffer flush cadence (seconds). Lower = less data loss on a JVM crash, higher = fewer DB
  # roundtrips. The buffer is also flushed when batch-size entries accumulate, on /scs reload,
  # and on plugin disable.
  flush-interval-seconds: 5
  # Max entries kept in the in-memory buffer before forcing a flush.
  batch-size: 20

When enabled, all claim actions (permission changes, flag changes, member management, etc.) are logged with timestamps and player information. Logs older than retention-days are automatically purged.

Buffering

Writes do not hit the database one row at a time. Entries are buffered in memory and flushed in two cases: every flush-interval-seconds on a timer, or as soon as batch-size rows accumulate — whichever comes first. The buffer is also drained synchronously on /scs reload and on shutdown, so the only realistic data-loss window is a hard JVM crash within the last flush-interval-seconds seconds.

Exporting logs to CSV

/scs exportAuditLogs <claimId|*>

Dumps the audit log of a specific claim — or all claims with * — into a CSV file under plugins/SimpleClaimSystem/audit-exports/. Useful for external review, compliance, or just keeping a copy before retention-days drops old rows.

Plugin Hooks

Every optional integration can be disabled with a single flag in config.yml. Useful when another plugin is installed for its own reasons and you don't want SCS2 talking to it, or when troubleshooting ("does this still happen with the Dynmap hook off?").

hooks:
  vault: true              # economy (claim buy/sell, tax)
  worldguard: true         # registers the scs-claim custom flag
  placeholderapi: true     # %scs_*% placeholders
  geyser: true             # Bedrock client detection
  floodgate: true          # Bedrock UUID mapping
  dynmap: true
  bluemap: true
  pl3xmap: true
  squaremap: true
  quickshop-hikari: true
  griefprevention: true
  lands: true
  towny: true
  nexo: true               # <glyph:...> and <shift:N> tags
  oraxen: true
  itemsadder: true
  pvpmanager: true         # block combat-tagged players from no-PvP claims (needs PvPManager)

Set any hook to false and SCS2 skips the entire setup — no retry timer, no listener registration, no performance cost. PacketEvents is a hard dependency and cannot be disabled here.

Disabling the vault hook automatically forces claims.economy.enabled to false (can't have economy features without an economy provider). Disabling map hooks removes the overlay; it doesn't delete stored claim geometry.

Bedrock Players

When Geyser/Floodgate is installed, SCS2 detects Bedrock clients and routes them through native forms (SimpleForm / CustomForm) instead of the Java chest GUIs. Two knobs control this:

bedrock:
  # true  → Bedrock players see the Java chest GUIs (translated by Geyser).
  # false → Bedrock players see the native forms defined under bedrock-guis/*.yml.
  force-java-menus: false

When to enable force-java-menus

  • Your resource pack uses Nexo/Oraxen glyphs that don't render on native Bedrock forms.
  • You want visual parity between Java and Bedrock clients.
  • You've customized Java GUIs heavily and don't want to maintain two separate UI trees.

Caveat

Chest GUIs translated by Geyser have quirks: shift-click doesn't always fire, some items render without custom model data, and closing via "E" vs the native close button behaves differently. For anything complex, the native Bedrock forms (below) give a better UX.