Portal Community

The Rule

Mandatory: All messaging secrets — Slack bot tokens, SMTP passwords, SendGrid API keys, Twilio Account SID/Auth Token pairs, FCM service account JSON, APNs p8 keys — are stored and retrieved exclusively via ICredentialResolver. Node configuration stores only an integer credentialId. Raw secrets in node config are a critical security defect.

ICredentialResolver

public interface ICredentialResolver
{
    /// Retrieve the secret value for the given credential ID.
    /// Always returns the decrypted secret string.
    /// Throws CredentialNotFoundException if the ID does not exist.
    Task<string> GetPasswordAsync(int credentialId, CancellationToken cancellationToken = default);
}

Credential Storage per Channel

ChannelStored ValueNotes
SlackBot OAuth token (xoxb-...)Single string
Email (SMTP)SMTP passwordUsername stored in non-secret config; only password is secret
Email (SendGrid)SendGrid API key (SG.xxx)Single string
SMS (Twilio)JSON: {"accountSid":"AC...","authToken":"..."}Both values needed; stored as JSON object
SMS (Vonage)JSON: {"apiKey":"...","apiSecret":"..."}Both values needed; stored as JSON object
Push (FCM)Full service account JSON keyMulti-line JSON; stored as-is
Push (APNs)JSON: {"p8Key":"...","teamId":"...","keyId":"...","bundleId":"..."}All 4 fields needed

Registering a Credential

Credentials are registered via the Credential Management API (not by hand-editing the database):

POST /api/credentials
Content-Type: application/json

{
  "name": "Acme Slack Bot Token",
  "type": "messaging",
  "channel": "slack",
  "tenantId": "tenant-acme",
  "secretValue": "xoxb-your-actual-bot-token-here"
}

// Response:
{
  "credentialId": 12,
  "name": "Acme Slack Bot Token",
  "type": "messaging",
  "createdAt": "2026-05-25T08:00:00Z"
}

The returned credentialId (e.g., 12) is what you place in the node's credentialId field.

Runtime Resolution

// Inside any IMessagingChannelAdapter.SendAsync():
var secret = await _credentials.GetPasswordAsync(config.CredentialId, ct);

// For channels that store composite JSON:
var twilioAuth = JsonSerializer.Deserialize<TwilioCredential>(secret)!;
TwilioClient.Init(twilioAuth.AccountSid, twilioAuth.AuthToken);

Credential Rotation

When a secret is rotated (e.g., Slack token revoked and reissued), update the stored credential value via:

PUT /api/credentials/12/secret
{
  "secretValue": "xoxb-new-token-value"
}

No node configuration changes are required — the credentialId integer remains the same. All running and future executions automatically use the new secret on the next call to GetPasswordAsync.

Audit trail: The credential management system maintains an audit log of all secret access events keyed by credentialId, execution ID, and actor. This provides a complete access history without the secret value ever appearing in logs.