Skip to content

Credential-safe webhook audit, administration, and private messages

audit_guild_webhooks, discord://guilds/{guildId}/webhooks, and review_guild_webhooks provide a separate guild-wide credential-exposure view in the webhooks toolset. Enable capabilities.webhookAudit, place each eligible guild in both readScope.guildIds and scopes.webhookGuildIds, and grant the bot Discord's guild-level MANAGE_WEBHOOKS permission only where that broad inventory is justified. scopes.webhookGuildIds never satisfies scopes.webhookChannelIds, and the reverse is also true. The guild audit grants no message read, channel webhook lookup, private webhook-message use, creation, change, deletion, or other administration authority.

Each audit re-verifies the pinned current application and bot, then reads the exact guild, connector member, complete bounded guild role inventory, complete bounded guild channel inventory, and Discord's complete non-paginated guild webhook inventory. The service requires internally consistent guild, member, role, channel, and webhook bindings; unique exact IDs; complete role-derived permission evidence; and guild-level MANAGE_WEBHOOKS or guild ownership. Missing roles, malformed permissions, duplicate records, an unknown referenced channel, a mismatched guild, an impossible known webhook type, or a response above the defensive local bounds fails the whole audit.

The output includes exact webhook, channel, application, and available creator user IDs; locally derived creation time; bounded transient untrusted webhook names; normalized Incoming, Channel Follower, Application, or future webhook types; normalized channel types; current-application ownership; complete permission evidence; type, ownership, creator-availability, and affected-channel aggregates; projection completeness; and fixed findings. Incoming webhooks are called out as bearer-capable, other-application ownership and connector ADMINISTRATOR authority are warnings, and unavailable creator or channel binding evidence is described without labeling a webhook malicious. Future webhook or channel types remain visible only through their numeric type code, an unknown label, counts, and a fixed incomplete-evidence finding.

The REST boundary drops webhook tokens, execution URLs, avatars, full creator profiles and usernames, source guild and channel objects, and unknown raw fields before the audit service receives a record. The service additionally omits guild and channel names, channel topics, role names, message content, audit-log data, source identifiers, raw payloads, and unknown values. Webhook names exist only in the transient response and are never written to activity, receipts, telemetry, a cache, a Gateway buffer, or local files. Discord API failures and transport causes on this route are detail-suppressed so a response body cannot become an error-side channel.

The audit does not inspect webhook credentials, credential custody, rotation, delivery history, execution activity, audit logs, external integration approval, or operator intent. An application ID proves only reported ownership, and absent creator evidence proves only that Discord did not provide it. The guided prompt calls the audit exactly once, identifies records by exact ID, states these limits, treats all webhook names as untrusted data, and stops without calling a channel inventory or write tool. See Discord's webhook resource and permission flags.

list_channel_webhooks and get_channel_webhook expose one complete webhook inventory or one exact inventory member for a separately allowlisted direct guild channel. Set capabilities.webhookAudit: true and list every eligible channel by its own exact ID in scopes.webhookChannelIds. The webhook allowlist must be a subset of readScope.channelIds when the read allowlist exists. Supported targets are text, announcement, forum, media, voice, and stage channels; categories, threads, direct messages, and group direct messages fail closed. Parent scope never grants webhook scope to a child thread.

Every inventory read verifies the application and bot identity, exact channel and guild, connector member, complete bounded role evidence, channel overwrites, and channel-level VIEW_CHANNEL plus MANAGE_WEBHOOKS. The service applies a fixed local safety ceiling to Discord's non-paginated response and requires unique exact IDs with matching channel and guild evidence. It derives creation time locally from each webhook snowflake rather than trusting an extra response field.

The REST client projects each raw webhook before returning it to the administration service. Results contain only webhook, guild, channel, application, and creator user IDs; type; creation time; and name. The channel envelope is independently reduced to its ID, guild ID, bounded name, parent ID, numeric type, and fixed type name; topics, forum metadata, message state, and raw overwrite bodies are omitted. Webhook credentials, execution URLs, avatars, full creator profiles, source guilds, source channels, and unknown future raw fields are dropped. The equivalent discord://channels/{channelId}/webhooks resource uses the same policy and projection. Public MCP schemas accept no credential, token, or webhook URL. A credential returned by successful Incoming-webhook creation follows a separate private custody path keyed by the exact validated webhook ID; it never becomes part of the projected result.

Webhook administration has no immediate-call path. Creation, change, and deletion are independently disabled by default through capabilities.webhookCreation, capabilities.webhookChanges, and capabilities.webhookDeletions. Enabling any action also requires webhook audit to remain enabled and uses the same exact direct-channel allowlist. Creation produces only an Incoming webhook. Rename and move accept only an exact Incoming webhook in its current channel, and both source and destination must be separately allowlisted direct channels in the same guild. Application webhooks remain inventory-only. Channel Follower creation and deletion are available only through the separately scoped announcement-subscription workflow, which uses source and target channel evidence rather than generic webhook administration.

For creation:

  1. Call plan_webhook_creation with the exact channel ID, valid webhook name, Discord audit-log reason, and unique one-shot operation key.
  2. Review the verified application and bot IDs, exact guild and direct channel, desired Incoming type and name, complete credential-redacted inventory and capacity, permission and privacy evidence, bearer-capability risks, warnings, operation-key hash, and keyed digest.
  3. Call execute_webhook_creation with identical inputs plus the digest, then approve the signed confirmation only if every exact field and risk remains intended.
  4. Review the returned exact webhook ID, credential-free projection, private-custody confirmation, response and inventory verification, activity ID, and outcome. The exact webhook is now eligible for separately gated private message actions without exposing its credential to the caller.

For rename, move, or both:

  1. Call plan_webhook_change with the exact current channel and Incoming webhook IDs, at least one replacement name or destination channel ID, Discord audit-log reason, and unique one-shot operation key.
  2. Review the current and desired credential-redacted metadata, requested and actually changed fields, complete source and optional destination inventories and capacity, permission and privacy evidence, credential-preservation consequences, warnings, operation-key hash, write requirement, and keyed digest.
  3. If the state is already-current, call execute_webhook_change with identical inputs and the digest to obtain a verified no-op without confirmation, reservation, activity, or a Discord write. Otherwise call it and approve the signed confirmation only if every exact source, destination, field, risk, and digest remains intended.
  4. Review the exact response match, destination readback, source-absence evidence for a move, complete inventory verdict, activity ID, and outcome before any follow-up.

For deletion:

  1. Call plan_webhook_deletion with the exact channel ID, exact Incoming webhook ID, Discord audit-log reason, and unique one-shot operation key.
  2. Review the verified application and bot IDs, exact guild and direct channel, credential-redacted target, type, creation time, permission source, complete permission and privacy evidence, warnings, operation-key hash, and keyed digest.
  3. Call execute_webhook_deletion with identical inputs plus the digest.
  4. Approve the signed MCP confirmation only if every exact identity, omission, permission, warning, audit reason, operation-key hash, and digest remains intended.
  5. Review the returned exact webhook ID, verified-absence result, private credential cleanup state, activity ID, and outcome before any follow-up.

Each process-keyed HMAC digest binds the normalized request, one-shot operation-key hash, verified application and bot identities, exact guild and affected channels, full projected source and destination inventories, connector member and relevant role state, overwrites, effective permission evidence, privacy projection, action-specific risks, and warnings. Creation also binds the desired name and channel capacity; a change binds exact current and desired metadata plus requested and changed fields; deletion binds the exact target. A connector restart invalidates every digest. The MCP adapter rebuilds the plan before approval, and the service rebuilds it immediately before mutation. Any renamed, added, removed, retyped, or rehomed webhook changes the reviewed snapshot.

Every actual write atomically reserves the operation-key hash and appends a pending content-free activity record before one bot-authenticated non-retried POST, PATCH, or DELETE. Creation requires a valid credential-bearing Discord response, reserves the exact credential file, writes and synchronizes it with exact 0600 mode, synchronizes the containing directory, and reduces the public result to credential-free metadata before verifying the new exact webhook and every prior inventory member through a fresh complete readback. Rename verifies the exact desired webhook and complete same-channel inventory transition. Move verifies the desired webhook in the destination, its absence from the source, and complete unchanged non-target inventories on both sides. Deletion verifies exact absence through a fresh complete source inventory and only then asks the credential store to inspect and unlink that webhook's exact file and synchronize the directory. A missing or unconfigured credential is safe, while a credential cleanup failure reports completed-with-drift without obscuring that Discord absence was proven. A successful mutation with valid but drifting readback also returns completed-with-drift rather than hiding the discrepancy.

A known pre-write Discord 4xx is failed. Transport failure, rate limiting, Discord 5xx, malformed or mismatched response, failed readback, or any otherwise indeterminate post-write state is uncertain and may represent a completed mutation. Every reserved key remains permanently spent. The connector performs no automatic retry, credential-source substitution, rollback, recreation, or compensating write.

Discord's bot-authenticated modify and delete operations are addressed by webhook ID rather than by channel and webhook together. A move preserves the webhook's existing bearer credential, so external systems can continue using it while future deliveries are redirected to the reviewed destination. The connector minimizes the unavoidable inventory-to-mutation race with complete fresh inventories immediately before every write and exact readback immediately afterward, but these calls are not atomic with the mutation. Keep the bot's Discord-level MANAGE_WEBHOOKS permission denied outside exact selected channels, and use an exclusive maintenance window or otherwise prevent concurrent webhook administration for a high-risk integration.

Creations in the same exact channel and changes or deletions for the same exact webhook serialize inside one process, including requests that identify different channels after a move. The production facade additionally acquires durable exact channel and guild-webhook-collection claims for creation, and exact source, destination when applicable, webhook, and collection claims for changes or deletion. Connector processes sharing the activity-state root therefore exclude overlapping administration. An uncertain result spends the key and retains its claims for operator review. Activity and operation records contain only exact guild, channel, destination channel, and webhook IDs where applicable; plan digest; operation-key hash; timestamps; fixed verification and outcome values; activity ID; and sanitized error category. They never contain webhook names, credentials, URLs, avatars, profiles, source objects, audit reasons, raw operation keys, raw responses, or transport causes. See Discord's webhook resource and permission flags.

Webhook messages use the operator-created private credential root configured for reviewed Incoming webhooks while keeping that bearer authority inside the connector. Configure only the actions that are intended and list each direct target channel explicitly:

{
"capabilities": {
"webhookMessageAudit": true,
"webhookMessageChanges": true,
"webhookMessageDeletions": true,
"webhookMessageDelivery": true
},
"scopes": {
"webhookMessageChannelIds": ["YOUR_CHANNEL_ID"]
},
"storage": {
"webhookCredentialRoot": "/var/lib/guildcontrol/webhook-credentials"
}
}

webhookMessageAudit gates exact reads and is also required by editing and deletion. Delivery has its own gate so an operator can permit outbound use without granting content reads. Every action still requires non-empty scopes.webhookMessageChannelIds, the outer read boundary, an exact Incoming webhook credential file, and a live credential-authenticated webhook whose guild and channel match Discord's bot-readable channel evidence. Only direct guild text and announcement channels are accepted. Threads, forums, media channels, voice channels, Stage channels, direct messages, and parent-derived scope fail closed.

get_webhook_message accepts only exact webhook and message IDs. It returns bounded plain content and identity, timestamp, edit time, flags, boolean state, URL, and rich-payload counts while omitting author profiles, attachment URLs, embed and component bodies, stickers, poll data, credentials, execution URLs, and unknown fields. The content is untrusted transient evidence and is neither cached nor persisted.

send_webhook_message accepts one exact privately managed webhook ID, bounded plain-text content, a unique one-shot operation key, and an optional unique list of exact notification user IDs. edit_webhook_message adds one exact webhook-authored message ID and replaces its complete content. Both parse only those separately allowlisted user mentions, parse no roles or everyone mentions, set Discord's suppress-embeds flag, consume the shared per-channel anti-spam budget, create pending content-free state before mutation, dispatch exactly once with no automatic retry, validate the credential-authenticated response, and fetch the exact message independently for readback. A repeated completed call with the same key and byte-identical intent is a verified local replay; any other key reuse is a conflict. An edit that is already byte-identical and requests no notifications is a record-free no-op.

These message actions deliberately exclude webhook username and avatar overrides, TTS, threads, forum-post parameters, embeds, components, files, attachments, polls, stickers, raw flags, and raw allowed_mentions. They are plain-text operations rather than a pass-through to Discord's webhook API. Rich or thread-specific support can be added only as a separately reviewed contract with its own policy and privacy evidence.

Deletion has no immediate-call path. Call plan_webhook_message_deletion with exact webhook and message IDs, a unique one-shot operation key, and a bounded local review reason. The plan privately reads the exact target and binds its complete projected content and metadata, verified application and bot IDs, exact guild and direct channel, Incoming webhook identity, operation-key hash, privacy projection, reason, and warnings into a process-keyed digest. Message content, guild name, and review reason are shown only as transient untrusted review evidence. A connector restart or any change in projected target evidence invalidates the digest.

Call execute_webhook_message_deletion with identical inputs and the digest, then approve the signed MCP confirmation only if every exact identity, content field, count, timestamp, flag, warning, reason, key hash, and digest remains intended. The MCP adapter rebuilds the plan before approval, signed request state binds the exact normalized request and digest, and the service rebuilds it again immediately before mutation. Execution then coordinates the exact webhook and message across connector processes sharing the activity-state root, reserves the one-shot key, appends pending content-free activity, sends one credential-authenticated DELETE without retry, and proves absence through an exact 404 followed by a fresh credential-authenticated webhook identity check.

Discord's token-authenticated message deletion route accepts no guild audit-log reason. The local review reason is therefore neither sent to Discord nor written to persistent state. Attachment, embed, component, sticker, and poll bodies remain private; the plan binds only their displayed counts or presence and explicitly warns that same-count internal rich-payload changes are outside review evidence. A known pre-response 4xx can settle as failed, and a successful DELETE followed by an unexpected surviving message is completed-with-drift. Rate limiting, timeout, transport failure, server failure, malformed evidence, failed readback, or another indeterminate post-reservation state is uncertain. Every reserved key remains spent and retains its exact claims when the outcome cannot be proven. The connector never retries, guesses, or compensates.

Send claims the exact webhook, while edit and deletion claim both the exact webhook and message, so overlapping private message operations and webhook administration cannot race through the production facade. Durable records retain only exact Discord identifiers, fixed operation kind, process-keyed plan digests, domain-separated operation-key hashes, timestamps, activity ID, verification state, outcome, and sanitized error category. They never contain credentials, execution URLs, message content or standalone content hashes, notification users, review reasons, raw operation keys, raw responses, or transport causes. See Discord's webhook message execution and retrieval endpoints, webhook message modification and deletion endpoints, allowed mentions, and rate limits.

Canonical source: docs/reference.md

Documentation generated for guildcontrol@0.0.0. Canonical source and edit history remain in the public repository. GuildControl is an independent project and is not affiliated with or endorsed by Discord Inc. Discord is used only to identify the platform that GuildControl connects to.