Docs / Developers

Connect API

Put a Connect button on your platform: your users link their collection to their X identity in one click.

Prefer plain text, or handing this to an assistant? Read it as markdown.

On this page What this is for

What this is for#

You already know which wallets belong to which user. You send us that list, signed with your secret, and we take it as given: the wallets in a claim are that user's. There is nothing for the collector to paste, export or sign.

Identity stays on our side. They sign in with X here, so you are never asked for a handle and never have to store one.

The API is a signed link: your server builds it, your user opens it. There are no JSON calls to make and nothing to poll. On your platform the button reads Connect with Vollector.

The claim#

FieldTypeNotes
vnumberrequiredFormat version. Always 1 today.
walletsarrayrequiredUp to 10 entries of { address, chain }.
noncestringrequiredUnique per link, at least 8 characters. Redeeming burns it.
iatnumberrequiredIssued at, seconds since epoch.
expnumberrequiredExpiry, seconds since epoch. At most 600 seconds after iat.
uidstringoptionalYour own id for this user. Opaque to us, never shown.
redirect_uristringoptionalWhere the Back button sends them. Must be an origin you registered.

Supported chains: solana, ethereum, polygon, base, abstract, bsc, evm.

Reference implementation#

Node, and nothing outside its standard library. Any language with HMAC-SHA256 and base64url does the same job.

import { createHmac, randomBytes } from "node:crypto";

const PARTNER_ID = "your-partner-id";
const SECRET = process.env.VOLLECTOR_SECRET;

export function connectUrl(wallets, opts = {}) {
  const iat = Math.floor(Date.now() / 1000);
  const claim = {
    v: 1,
    wallets,                       // [{ address, chain }]
    nonce: randomBytes(16).toString("hex"),
    iat,
    exp: iat + 600,
    uid: opts.uid,                 // your id for this user, optional
    redirect_uri: opts.redirectUri // must be a registered origin, optional
  };

  const d = Buffer.from(JSON.stringify(claim)).toString("base64url");
  const s = createHmac("sha256", SECRET)
    .update(`v1.${PARTNER_ID}.${d}`)
    .digest("base64url");

  return `https://api.vollector.id/v1/connect?p=${PARTNER_ID}&d=${d}&s=${s}`;
}

Test vector#

Run your implementation against this before you call us. If your signing input and your signature match these strings, your integration is correct and any remaining problem is on our side. The secret below is published here, so it is valid for nothing.

partner_id
example
secret
vollector-test-secret
claim
{
  "v": 1,
  "wallets": [
    {
      "address": "0x8a981c2cfdd7fbc65395dd2c02ead94e9a2f65a7",
      "chain": "base"
    },
    {
      "address": "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
      "chain": "solana"
    }
  ],
  "nonce": "b7c1e0a2f4d94e3a",
  "iat": 1767225600,
  "exp": 1767226200,
  "uid": "user_12345",
  "redirect_uri": "https://example.com/settings?vollector=done"
}
d (base64url payload)
eyJ2IjoxLCJ3YWxsZXRzIjpbeyJhZGRyZXNzIjoiMHg4YTk4MWMyY2ZkZDdmYmM2NTM5NWRkMmMwMmVhZDk0ZTlhMmY2NWE3IiwiY2hhaW4iOiJiYXNlIn0seyJhZGRyZXNzIjoiOVd6RFh3QmJta2c4WlRiTk1xVXh2UVJBeXJaekRzR1lkTFZMOXpZdEFXV00iLCJjaGFpbiI6InNvbGFuYSJ9XSwibm9uY2UiOiJiN2MxZTBhMmY0ZDk0ZTNhIiwiaWF0IjoxNzY3MjI1NjAwLCJleHAiOjE3NjcyMjYyMDAsInVpZCI6InVzZXJfMTIzNDUiLCJyZWRpcmVjdF91cmkiOiJodHRwczovL2V4YW1wbGUuY29tL3NldHRpbmdzP3ZvbGxlY3Rvcj1kb25lIn0
signing input
v1.example.eyJ2IjoxLCJ3YWxsZXRzIjpbeyJhZGRyZXNzIjoiMHg4YTk4MWMyY2ZkZDdmYmM2NTM5NWRkMmMwMmVhZDk0ZTlhMmY2NWE3IiwiY2hhaW4iOiJiYXNlIn0seyJhZGRyZXNzIjoiOVd6RFh3QmJta2c4WlRiTk1xVXh2UVJBeXJaekRzR1lkTFZMOXpZdEFXV00iLCJjaGFpbiI6InNvbGFuYSJ9XSwibm9uY2UiOiJiN2MxZTBhMmY0ZDk0ZTNhIiwiaWF0IjoxNzY3MjI1NjAwLCJleHAiOjE3NjcyMjYyMDAsInVpZCI6InVzZXJfMTIzNDUiLCJyZWRpcmVjdF91cmkiOiJodHRwczovL2V4YW1wbGUuY29tL3NldHRpbmdzP3ZvbGxlY3Rvcj1kb25lIn0
s (signature)
fjL7l4tymKmO6lHglIY_Ga5mkQ_LbNE9fEilWrDChvA
full URL
https://api.vollector.id/v1/connect?p=example&d=eyJ2IjoxLCJ3YWxsZXRzIjpbeyJhZGRyZXNzIjoiMHg4YTk4MWMyY2ZkZDdmYmM2NTM5NWRkMmMwMmVhZDk0ZTlhMmY2NWE3IiwiY2hhaW4iOiJiYXNlIn0seyJhZGRyZXNzIjoiOVd6RFh3QmJta2c4WlRiTk1xVXh2UVJBeXJaekRzR1lkTFZMOXpZdEFXV00iLCJjaGFpbiI6InNvbGFuYSJ9XSwibm9uY2UiOiJiN2MxZTBhMmY0ZDk0ZTNhIiwiaWF0IjoxNzY3MjI1NjAwLCJleHAiOjE3NjcyMjYyMDAsInVpZCI6InVzZXJfMTIzNDUiLCJyZWRpcmVjdF91cmkiOiJodHRwczovL2V4YW1wbGUuY29tL3NldHRpbmdzP3ZvbGxlY3Rvcj1kb25lIn0&s=fjL7l4tymKmO6lHglIY_Ga5mkQ_LbNE9fEilWrDChvA

Trying it live#

Add &dry=1 to any connect link. It checks the link and reports what we saw, without signing anyone in and without writing anything. It works before your integration is switched on, so you can build the whole thing against it.

When something is rejected#

ReasonWhat it means
bad_signatureThe HMAC does not match. Check the signing input, not the payload.
malformedThe payload is not base64url JSON, or a required field is missing.
bad_versionThe v field is not 1.
expiredexp is in the past. Links last ten minutes.
not_yet_validiat is more than a minute in the future. Usually a clock problem.
ttl_too_longexp is more than 600 seconds after iat.
no_walletsThe wallets array is empty.
too_many_walletsMore than 10 wallets in one link.
bad_walletA wallet has no address.
bad_chainA wallet is on a chain we do not index.

Users see plain language, not these codes. The dry run shows you the code.

The button#

Implement it however you want, wherever you want. The mark as SVG, and as a square icon for dark and light interfaces.

Vollector mark

Mark

SVG, takes the color of the text around it: next to a price, in a button.

Vollector icon, dark

Icon, dark

White mark on black, square: avatars, source lists, app grids on dark interfaces.

Vollector icon, light

Icon, light

Black mark on white, square: the same, on light interfaces.

Getting your credentials#

Tell us your platform name and the exact origins we should allow as return URLs, and we send back a partner id and a secret. Keep the secret on your server, never in client code. It can be rotated at any time, and the old one keeps working alongside the new one until you tell us you have finished deploying.

You can build and test the whole integration with a dry run before we switch you on. See Trying it live.