Custom Plugins

Custom plugins are HTTP endpoints that extend claim logic. BitBadges sends a POST request to your endpoint during simulation, execution and/or post-success, and your response determines whether the plugin passes or fails.

Any logic you can express as an HTTP handler can become a claim plugin β€” authentication checks, API calls, database lookups, AI evaluations, webhook triggers, or anything else.

Getting Started

  1. Go to bitbadges.io/developerarrow-up-right β†’ Plugins tab

  2. Create a new plugin

  3. Configure endpoint URL, schemas, and settings

  4. Implement your handler

  5. Test with the built-in claim tester

  6. Finalize the version

  7. Use the plugin in claims by its plugin ID

Architecture

User claims β†’ BitBadges sends POST to your endpoint β†’ You return 200 OK or error

Three parties:

  • Claim Creator β€” configures the claim, selects plugins, sets public/private params

  • Claiming User β€” attempts the claim, provides user inputs

  • Plugin Creator β€” builds and maintains the plugin endpoint (you)

The claim creator configures public/private params when adding the plugin to a claim. The user provides user inputs at claim time. All three are merged into a single request payload sent to your handler.

Handler Implementation

Context Info Reference

Every request includes these context fields:

Additionally, pluginSecret and version are included in every request. All custom params (public, private, user inputs) are merged into the top level of the request body alongside these fields.

Parameters

Three types of parameters, all merged into the handler payload:

  • Public params β€” Configured by the claim creator when adding the plugin to a claim. Visible in the claim's public configuration. Claim users can see these. Example: minimum token balance, a URL to display.

  • Private params β€” Configured by the claim creator. Hidden from claim users, only visible to BitBadges and the claim creator. Example: API keys, internal thresholds, webhook URLs.

  • User inputs β€” Provided by the user at claim time via a form rendered by BitBadges (or a custom frontend). Example: a code, an answer to a question, a file upload URL.

All three are defined as JsonBodyInputSchema[] arrays in your plugin's version config.

User Inputs

Define input schemas when creating your plugin. BitBadges renders a form for the user based on your schema.

Each field is defined as:

Supported types:

  • string β€” free text input

  • number β€” numeric input

  • boolean β€” checkbox/toggle

  • date β€” date picker (value is UNIX ms)

  • url β€” URL input with validation

Custom Frontend

If the in-site form isn't sufficient, redirect users to your own UI via a redirect URL. Typical flow:

  1. User is redirected to your frontend

  2. User authenticates / completes action on your side

  3. You issue a one-time code or token

  4. User enters the code back in-site

  5. Your handler validates the code

Response Presets

Configure your plugin's stateFunctionPreset when creating a version. This determines how BitBadges handles your response and manages state.

Stateless

Return 200 OK with an empty body. Plugin passes if status is 200. No state is tracked.

Best for: validation checks, API verifications, webhook triggers β€” anything where you just need pass/fail.

ClaimToken

Return a one-time use token that you generate. BitBadges tracks which tokens have been used. If the overall claim succeeds, the token is marked as used. If the claim fails (another plugin fails), the token remains available for retry.

Best for: custom authentication flows, external code validation, third-party integrations that issue tokens.

ClaimNumbers

Return the claim number to assign for this attempt. Only one plugin per claim can use this preset.

Best for: custom claim number assignment logic (non-sequential, based on external data, etc.).

CustomResponseHandler

Full control over state management and response handling. You manage everything yourself.

Best for: advanced use cases that don't fit the other presets.

Error Handling

  • Timeout: All responses must arrive within 10 seconds. If your endpoint doesn't respond in time, the plugin fails.

  • Error format: Return { "message": "..." } with a non-200 status code. The message may be shown to the user β€” be informative but don't reveal secrets or internal details.

  • Key restriction: Do not use . in returned JSON keys (e.g., use bob@abc[dot]com not [email protected]). This is a storage constraint.

Simulations (Dry Runs)

BitBadges simulates claims before real submission. Your plugin receives a simulation request first, then (if simulation passes) the real execution request.

Detection: _isSimulation === true and claimAttemptId is empty.

Your plugin should:

  • Validate inputs and check preconditions

  • Return the appropriate status code (200 = would pass, non-200 = would fail)

  • NOT execute side effects, mutate state, send notifications, or consume tokens

You can opt out of simulation requests by setting ignoreSimulations: true in your version config. If opted out, your plugin won't be called during simulation β€” it will only be called during real execution.

State Management

Critical rule: A 200 from your plugin does NOT mean the overall claim succeeded. Other plugins in the pipeline may fail, causing the claim to fail even though your plugin passed.

Use response presets (Stateless, ClaimToken, ClaimNumbers). BitBadges only commits state changes if:

  1. Your plugin returned 200, AND

  2. The overall claim succeeded (all required plugins passed per the success logic)

This eliminates race conditions and ensures consistency. If the claim fails, any state your plugin would have changed is rolled back.

Self-Managed State

If you manage state in your own backend (external database, third-party API, etc.):

  • Don't update state on plugin success. Your plugin passing doesn't mean the claim succeeded.

  • Verify claim outcomes via the API before committing external state changes:

  • Handle race conditions. Multiple claim attempts can execute concurrently. Use idempotency keys or atomic operations in your backend.

  • Consider using receiveStatusWebhook β€” BitBadges can POST to your endpoint with the final claim outcome, so you don't have to poll.

Success Webhooks & Retry Strategy

If your plugin version has receiveStatusWebhook: true, BitBadges POSTs to your endpoint after the claim completes with _attemptStatus: 'success' or 'failure' in the body. This is separate from the plugin validation call.

Webhooks use exponential backoff for retries:

  • Base delay: 1 hour

  • Formula: 2^retries Γ— base_delay

  • Max delay: 7 days

  • Example: 1h β†’ 2h β†’ 4h β†’ 8h β†’ 16h β†’ ...

Design your webhook handler to be idempotent β€” the same webhook may be delivered multiple times due to retries. Use the claimAttemptId as a deduplication key.

Plugin Version Config

Each plugin version contains the full configuration. When creating or updating a version in the developer portal, you configure all of these fields:

Field Details

Field
Description

finalized

Once finalized, a version is immutable and available to all users. Unfinalized versions are only usable by the plugin creator (for testing).

duplicatesAllowed

If true, a claim can include multiple instances of this plugin (each with different params). If false, only one instance is allowed.

requiresSessions

If true, the claim requires an active BitBadges session (user must be signed in).

requiresUserInputs

If true, the user must provide inputs at claim time (BitBadges renders a form). If false, the plugin operates with only creator-configured params.

reuseForNonIndexed

If true, the plugin is compatible with on-demand claims (stateless evaluation, no claim action needed). Requires the plugin to be stateless with no user inputs.

receiveStatusWebhook

If true, BitBadges POSTs to your endpoint after the claim completes with the final outcome. Useful for self-managed state β€” you learn whether the claim succeeded or failed without polling.

skipProcessingWebhook

If true, BitBadges auto-passes this plugin without calling your endpoint. Useful for display-only plugins (like custom-instructions) that don't need server-side validation.

ignoreSimulations

If true, your endpoint is not called during simulation/dry-run requests. The plugin auto-passes simulation.

requireSignIn

If true, the user must be signed in to BitBadges before this plugin is evaluated.

hardcodedInputs

Static key-value pairs always included in requests to your endpoint. Useful for API keys or configuration that doesn't change per claim.

passAddress

If true, the user's bitbadgesAddress and ethAddress are included in the payload. If false, the plugin operates without knowing the user's address.

customDetailsDisplay

A template string displayed to users in the claim UI. Use {{key}} to reference public param values. Example: "Must own at least {{minBalance}} tokens from collection {{collectionId}}".

Versioning

Plugins support multiple versions for safe iteration:

  • Creating versions: Each new version gets an incrementing version number. Start unfinalized for testing.

  • Finalizing: Once finalized, a version is immutable β€” schemas, endpoint URL, and all settings are locked. This protects claims that depend on your plugin from breaking changes.

  • Version selection: When a claim creator adds your plugin, they use the latest finalized version. That version is locked to the claim β€” even if you release new versions, existing claims stay on the version they were created with.

  • Testing unfinalized versions: Only the plugin creator can use unfinalized versions. Use the Claim Tester in the developer portal to test before finalizing.

  • Handler versioning: Your handler receives version and createdAt in every request. Use these to handle version-specific logic if needed (e.g., backwards-compatible changes to your handler that serves multiple versions).

Version Lifecycle

Publishing to the Plugin Directory

Plugins can be published to the BitBadges plugin directory, making them discoverable by other users.

  • Private plugins (default) β€” only you can add them to claims. Useful for internal tools, custom integrations, or plugins that require your specific backend.

  • Published plugins β€” visible in the plugin directory. Anyone can add them to their claims. Useful for building reusable tools that serve the broader BitBadges ecosystem.

To publish, configure your plugin's visibility settings in the developer portal. Published plugins should have clear descriptions, well-documented schemas, and stable, finalized versions.

Users can discover published plugins via the API:

Design Considerations

Plugin Success β‰  Claim Success

Your plugin may pass but the claim fails (other plugins fail). Or your plugin fails but the claim succeeds (OR logic). Only update external state based on verified claim outcomes.

Parallel Execution

All plugins in a claim execute in parallel. Your plugin cannot depend on another plugin's state mutations within the same claim attempt. Each plugin sees state as it was before the attempt started.

Async Processing

Claims are processed asynchronously via a queue. Don't depend on real-time BitBadges claim state (like total claims completed) β€” it may be stale by the time your plugin runs. Your own parameters and the context info passed to your handler are safe to depend on.

Nested Claim Depth

If your plugin uses the satisfies-claim pattern (checking another claim's success), be aware that nested on-demand claims have a depth limit of 5 levels. Circular references are detected and rejected.

On-Demand Compatibility

For on-demand claims (stateless, no explicit claim action), your plugin must be:

  • Stateless β€” no per-attempt state tracking

  • No user inputs β€” operates with just address + hardcoded params

  • Context-only β€” works with only the passed context info

Set reuseForNonIndexed: true in your version config.

Authentication

BitBadges does not handle your authentication. If you need the user to authenticate with your service, manage auth end-to-end yourself. Common pattern:

  1. User visits your frontend and authenticates

  2. You issue a one-time code

  3. User enters the code as a user input in-site

  4. Your handler validates the code

Security

  • Verify pluginSecret on every request to confirm BitBadges is the caller

  • Never expose pluginSecret client-side β€” it should only exist on your backend

  • Sanitize all inputs β€” user inputs are untrusted data from the claiming user

  • Rate-limit your endpoint independently of BitBadges β€” your endpoint is publicly addressable

  • Don't reveal internal details in error messages β€” they may be shown to the user

Testing

curl

Developer Portal

  • "Send Test Request" button sends mocked data to your endpoint β€” verify your handler responds correctly

  • Claim Tester β€” test the full claim flow with your plugin included. This is the only place to test unfinalized versions in a real claim context.

Request Bin

Use webhook.sitearrow-up-right or similar to inspect incoming payloads during development. Set your plugin's endpoint to the request bin URL, trigger a test, and examine exactly what BitBadges sends.

Internal Plugin Architecture

For reference, core (built-in) plugins follow the same pattern internally:

The key takeaway: state updates (toSet) are only applied if the overall claim succeeds. This is the same atomicity guarantee your custom plugins get with BitBadges-managed state presets.

Last updated