Key Takeaway: Twitter (X) lets you verify five user actions programmatically: follows, retweets, quote tweets, comments, and community membership. Each check is a single API call returning a boolean. Likes are no longer verifiable by any tool because X made them private in June 2024. Account ownership can be proven by checking the participant's recent timeline for a unique code.

Running a giveaway, ambassador program, or marketing campaign that rewards people for completing actions on X works for two minutes and then breaks. By participant three hundred the form fills up with fake handles, half-completed tasks, and bots harvesting the prize pool. Manual review is slow. Honor-system checkboxes are worthless. What you actually need is an API that answers, for every participant, "did this person do what they said they did?"

That is what an engagement verification API does, and it is the foundation underneath most modern giveaway platforms, quest tools, and brand-engagement workflows. Sorsa API exposes this as a small set of yes/no endpoints. Pass in the participant's handle and the action you want to verify, get a boolean back. In this guide I will walk through every available check, show working Python code for each, and stitch them into a full campaign verification pipeline (single participant first, then bulk processing for tens of thousands).

I have built or audited this pattern for dozens of clients over the past three years, mostly creator-marketing agencies and giveaway SaaS products. The endpoints look simple. The pitfalls (private accounts, fake handles, bot farms, the lost ability to check likes) are not. This guide covers both.


Table of Contents

  1. What You Can and Cannot Verify on X
  2. Check 1: Did the User Follow an Account?
  3. Check 2: Did the User Retweet a Tweet?
  4. Check 3: Did the User Quote a Tweet?
  5. Check 4: Did the User Comment on a Tweet?
  6. Check 5: Is the User a Community Member?
  7. Building a Full Campaign Verification Pipeline
  8. Verifying Participants in Bulk
  9. Verifying Account Ownership
  10. Anti-Fraud: Account Quality Checks
  11. Influence-Weighted Reward Scoring
  12. Case Study: 47,000 Participants in 14 Days
  13. Cost Estimation per Participant
  14. FAQ

What You Can and Cannot Verify on X

Here is the complete map of verification you can perform on X via API today, what endpoint each maps to, and what is no longer possible.

ActionEndpointMethodReturnsVerifiable?
User follows an account/check-followPOST{follow: true/false}Yes
User retweeted a tweet/check-retweetPOST{retweet: true/false}Yes
User quoted a tweet/check-quotedPOST{status: "quoted" / "retweet" / "not_found"}Yes
User commented on a tweet/check-commentGET{commented: true/false, tweet: {...}}Yes
User joined an X Community/check-community-memberPOST{is_member: true/false}Yes
User liked a tweet(none)(none)(none)No, private since 2024
User viewed/impressed a tweet(none)(none)(none)No

The likes restriction is the most common surprise. X announced via its engineering account on June 11, 2024 that likes would be private for everyone, and the change rolled out the same week. No API (including the official X API) can answer "did user A like tweet B" anymore. If you have old campaign templates that include "Like this post," replace that task with a retweet or comment. Both remain fully verifiable and produce stronger engagement signals anyway.

Everything else in the table above is a single API call against Sorsa API. Authentication is a single ApiKey header (no OAuth flow, no app approval), and the response is plain JSON. The rest of this guide is the practical how-to.


Check 1: Did the User Follow an Account?

The most common campaign task ("Follow @YourBrand to enter"). The /check-follow endpoint answers this directly. The endpoint's logic is "does user_2 follow user_1?" so user_1 is the brand and user_2 is the participant.

Endpoint: POST https://api.sorsa.io/v3/check-follow

Parameters

Provide one identifier for the brand (the followed account) and one for the participant.

ParameterTypeRequiredDescription
username_1stringOne ofThe brand's handle.
user_link_1stringtheseOr the brand's profile URL.
user_id_1stringOr the brand's numeric user ID.
username_2stringOne ofParticipant's handle.
user_link_2stringtheseOr participant's profile URL.
user_id_2stringOr participant's numeric user ID.

Python

python
import requests

API_KEY = "YOUR_API_KEY"
BASE = "https://api.sorsa.io/v3"
HEADERS = {"ApiKey": API_KEY, "Content-Type": "application/json"}

def check_follow(brand_handle: str, participant_handle: str) -> dict:
    resp = requests.post(
        f"{BASE}/check-follow",
        headers=HEADERS,
        json={"username_1": brand_handle, "username_2": participant_handle},
        timeout=15,
    )
    resp.raise_for_status()
    return resp.json()


result = check_follow("YourBrand", "participant123")
if result["follow"]:
    print("Follow verified.")
elif result.get("user_protected"):
    print("Account is private; follow cannot be confirmed.")
else:
    print("Not following.")

Response

json
{
  "follow": true,
  "user_protected": false
}

Edge cases to know

If user_protected is true, the participant's account is private and their follow graph is not visible to any third party. You have three options: reject the entry, ask the participant to make their account public for verification, or use account-ownership verification (covered below) to confirm they own the handle and then accept the entry on a manual override. In our experience, less than 1% of giveaway participants have private accounts, so a hard reject with a clear message is usually fine.


Check 2: Did the User Retweet a Tweet?

"Retweet this post to enter." Standard mechanic for boosting reach.

Endpoint: POST https://api.sorsa.io/v3/check-retweet

Parameters

ParameterTypeRequiredDescription
tweet_linkstringYesURL of the tweet to verify.
usernamestringOne ofParticipant handle.
user_linkstringtheseOr profile URL.
user_idstringOr numeric user ID.
next_cursorstringNoPagination for tweets with > 100 retweets.

Python

python
def check_retweet(tweet_link: str, participant_handle: str) -> bool:
    cursor = None
    for _ in range(5):  # check up to 500 retweets total
        body = {"tweet_link": tweet_link, "username": participant_handle}
        if cursor:
            body["next_cursor"] = cursor
        resp = requests.post(f"{BASE}/check-retweet", headers=HEADERS, json=body, timeout=15)
        resp.raise_for_status()
        data = resp.json()
        if data["retweet"]:
            return True
        cursor = data.get("next_cursor")
        if not cursor:
            return False
    return False

How pagination works

Each call scans the most recent 100 retweets. If your tweet has thousands of retweets and the user retweeted early, their action may be deeper in the list and require pagination. The example above caps at 5 pages (the most recent 500 retweets) to keep verification fast. For most campaigns this is more than enough because participants tend to retweet within hours of seeing the prompt, so their action sits at the top of the list.

For comparison, the official X API retweet lookup requires fetching the full retweeter list and searching it yourself, which means OAuth setup, per-endpoint rate-limit accounting, and your own pagination logic. Sorsa's /check-retweet returns the boolean directly in one call.


Check 3: Did the User Quote a Tweet?

"Quote this with your thoughts." This is more valuable than a plain retweet because the quote tweet adds the participant's own commentary and amplifies the campaign with personalized text.

Endpoint: POST https://api.sorsa.io/v3/check-quoted

The endpoint is smart about distinguishing a quote from a plain retweet, returning one of three statuses.

Python

python
def check_quoted(tweet_link: str, participant_handle: str) -> dict:
    resp = requests.post(
        f"{BASE}/check-quoted",
        headers=HEADERS,
        json={"tweet_link": tweet_link, "username": participant_handle},
        timeout=15,
    )
    resp.raise_for_status()
    return resp.json()


data = check_quoted("https://x.com/YourBrand/status/1234567890", "participant123")

if data["status"] == "quoted":
    print(f"Quote verified on {data['date']}: {data['text']}")
elif data["status"] == "retweet":
    print("Retweeted without commentary; does not satisfy quote requirement.")
else:
    print("No quote or retweet found.")

Why the quote text matters

The response includes the full quote text and date, which you can pipe into a quality check before approving the entry. A campaign that requires "quote with your thoughts on the new product" deserves more than a one-word quote like "nice". Most agencies I work with apply a minimum-character rule (typically 30 to 50 characters), a profanity check, and a required hashtag if the campaign uses one.

python
def quote_is_acceptable(quote_text: str, min_length: int = 30, required_hashtag: str = None) -> bool:
    if len(quote_text.strip()) < min_length:
        return False
    if required_hashtag and required_hashtag.lower() not in quote_text.lower():
        return False
    return True

Check 4: Did the User Comment on a Tweet?

"Drop a comment under this post." The only verification endpoint that uses GET instead of POST.

Endpoint: GET https://api.sorsa.io/v3/check-comment

Parameters (query string)

ParameterTypeRequiredDescription
tweet_linkstringYesURL of the tweet.
usernamestringOne ofParticipant handle.
user_linkstringtheseOr profile URL.
user_idstringOr numeric user ID.

Python

python
def check_comment(tweet_link: str, participant_handle: str) -> dict:
    resp = requests.get(
        f"{BASE}/check-comment",
        headers={"ApiKey": API_KEY},
        params={"tweet_link": tweet_link, "username": participant_handle},
        timeout=15,
    )
    resp.raise_for_status()
    return resp.json()


data = check_comment("https://x.com/YourBrand/status/1234567890", "participant123")

if data["commented"]:
    text = data["tweet"]["full_text"]
    print(f"Comment verified: {text[:120]}")
else:
    print("No comment found.")

Response and comment quality

When commented is true, the response includes the full tweet object of the comment itself: text, engagement metrics, language detection, timestamp. Use this to enforce minimum length, required keywords, or reject emoji-only spam replies. In a campaign where the comment is the entire engagement task, the quality bar should be higher than a single emoji.

python
def comment_is_acceptable(comment: dict, min_length: int = 20, required_keyword: str = None) -> bool:
    text = comment.get("full_text", "").strip()
    if len(text) < min_length:
        return False
    if required_keyword and required_keyword.lower() not in text.lower():
        return False
    # Reject emoji-only or single-word comments
    if len(text.split()) < 3:
        return False
    return True

Check 5: Is the User a Community Member?

"Join our X Community to participate." Useful when you want the participant to be a sustained part of the community rather than a one-shot retweeter.

Endpoint: POST https://api.sorsa.io/v3/check-community-member

python
def check_community_member(community_id: str, participant_handle: str) -> bool:
    resp = requests.post(
        f"{BASE}/check-community-member",
        headers=HEADERS,
        json={"community_id": community_id, "username": participant_handle},
        timeout=15,
    )
    resp.raise_for_status()
    return resp.json().get("is_member", False)


is_member = check_community_member("1966045657589813686", "participant123")
print("Member" if is_member else "Not a member")

The community ID is the long numeric string in the community URL (x.com/i/communities/<id>). Communities are often a more durable signal than a one-time retweet because joining a community signals intent to stay engaged.


Building a Full Campaign Verification Pipeline

In a real campaign, participants complete several tasks. Here is a pattern that runs all five checks for one participant, returns a structured result, and applies quality rules to the comment and quote.

python
from dataclasses import dataclass, field

@dataclass
class CampaignConfig:
    brand_handle: str
    tweet_to_retweet: str
    tweet_to_quote: str
    tweet_to_comment: str
    community_id: str
    required_hashtag: str = ""
    min_quote_length: int = 30
    min_comment_length: int = 20

@dataclass
class ParticipantResult:
    username: str
    follow: bool = False
    retweet: bool = False
    quote: bool = False
    quote_text: str = ""
    comment: bool = False
    comment_text: str = ""
    community: bool = False
    completed: int = field(init=False, default=0)

    def total(self) -> int:
        return sum([self.follow, self.retweet, self.quote, self.comment, self.community])


def verify_participant(username: str, cfg: CampaignConfig) -> ParticipantResult:
    r = ParticipantResult(username=username)

    # Follow
    r.follow = check_follow(cfg.brand_handle, username)["follow"]

    # Retweet
    r.retweet = bool(check_retweet(cfg.tweet_to_retweet, username))

    # Quote tweet (with quality check)
    quote_data = check_quoted(cfg.tweet_to_quote, username)
    if quote_data["status"] == "quoted":
        r.quote_text = quote_data.get("text", "")
        r.quote = quote_is_acceptable(r.quote_text, cfg.min_quote_length, cfg.required_hashtag)

    # Comment (with quality check)
    comment_data = check_comment(cfg.tweet_to_comment, username)
    if comment_data.get("commented"):
        r.comment_text = comment_data["tweet"].get("full_text", "")
        r.comment = comment_is_acceptable(comment_data["tweet"], cfg.min_comment_length)

    # Community
    r.community = check_community_member(cfg.community_id, username)

    r.completed = r.total()
    return r


cfg = CampaignConfig(
    brand_handle="YourBrand",
    tweet_to_retweet="https://x.com/YourBrand/status/111111111",
    tweet_to_quote="https://x.com/YourBrand/status/222222222",
    tweet_to_comment="https://x.com/YourBrand/status/333333333",
    community_id="1966045657589813686",
    required_hashtag="#YourLaunch",
)

result = verify_participant("participant123", cfg)
print(f"@{result.username}: {result.completed}/5 tasks done")

A single participant costs 5 API requests (one per task). At Sorsa's universal rate limit of 20 requests per second, one worker thread can verify roughly 4 participants per second sequentially. For most campaigns running verification once per submission, that is more than enough headroom.


Verifying Participants in Bulk

When a campaign has thousands of participants and you want to verify them in a batch (for example, before announcing winners), the pattern looks like this. Note the rate-limit handling, the CSV output, and the resumable design (writes a row immediately after each participant so a crash does not lose progress).

python
import csv
import time
from pathlib import Path

def verify_campaign_batch(usernames: list[str], cfg: CampaignConfig, output_file: str) -> None:
    fields = ["username", "follow", "retweet", "quote", "comment", "community",
              "completed", "quote_text", "comment_text"]

    already_done = set()
    out_path = Path(output_file)
    if out_path.exists():
        with out_path.open() as f:
            already_done = {row["username"] for row in csv.DictReader(f)}

    mode = "a" if out_path.exists() else "w"
    with out_path.open(mode, newline="") as f:
        writer = csv.DictWriter(f, fieldnames=fields)
        if mode == "w":
            writer.writeheader()

        for i, username in enumerate(usernames):
            if username in already_done:
                continue
            try:
                r = verify_participant(username, cfg)
                writer.writerow({
                    "username": r.username,
                    "follow": r.follow,
                    "retweet": r.retweet,
                    "quote": r.quote,
                    "comment": r.comment,
                    "community": r.community,
                    "completed": r.completed,
                    "quote_text": r.quote_text,
                    "comment_text": r.comment_text,
                })
                f.flush()
                print(f"[{i+1}/{len(usernames)}] @{username}: {r.completed}/5")
            except requests.HTTPError as e:
                if e.response.status_code == 429:
                    print("Rate limit hit, sleeping 5s and retrying...")
                    time.sleep(5)
                    continue
                print(f"[{i+1}] @{username}: ERROR {e}")

            time.sleep(0.25)  # stay safely under 20 req/s with 5 reqs per participant


participants = open("entries.txt").read().splitlines()
verify_campaign_batch(participants, cfg, "campaign_results.csv")

This pattern verifies roughly 14,000 participants per hour single-threaded. If you parallelize across two or three workers (still respecting the global 20 req/s ceiling), you can hit 30,000 per hour. For most campaigns under 100,000 entries, single-threaded sequential verification finishes overnight.


Verifying Account Ownership

Before a participant can win anything, you may want to prove they actually own the X handle they submitted. Without this step, anyone can paste a famous handle into your form and claim the reward. The standard pattern: generate a unique code, ask the participant to post a tweet containing it, then check their recent timeline for the code.

python
import secrets

def generate_verification_code(prefix: str = "VERIFY") -> str:
    return f"{prefix}-{secrets.token_hex(4)}"


def verify_account_ownership(username: str, expected_code: str) -> bool:
    """Check if the user posted a tweet containing the verification code."""
    resp = requests.post(
        f"{BASE}/user-tweets",
        headers=HEADERS,
        json={"username": username},
        timeout=15,
    )
    resp.raise_for_status()
    tweets = resp.json().get("tweets", [])
    for tweet in tweets:
        if expected_code in tweet.get("full_text", ""):
            return True
    return False


# Workflow
code = generate_verification_code()
print(f"Ask the user to post a tweet containing: {code}")
# ... user posts the tweet ...
if verify_account_ownership("participant123", code):
    print("Account ownership confirmed.")
else:
    print("Code not found in recent tweets.")

This is the same mechanism most serious giveaway and ambassador platforms use. The participant can delete the tweet after verification if they want, since you only need to confirm the post once.


Anti-Fraud: Account Quality Checks

Automated campaigns attract bots, and platforms running large-scale quest mechanics have invested heavily in sybil prevention as a result. A few API-level checks rule out the obvious offenders without needing a full sybil-detection system. Each one is one additional call to /info.

python
from datetime import datetime, timezone

def is_legitimate_account(
    username: str,
    min_age_days: int = 30,
    min_tweets: int = 10,
    min_followers: int = 5,
) -> tuple[bool, dict]:
    resp = requests.get(
        f"{BASE}/info",
        headers={"ApiKey": API_KEY},
        params={"username": username},
        timeout=15,
    )
    resp.raise_for_status()
    profile = resp.json()

    created = datetime.fromisoformat(profile["created_at"].replace("Z", "+00:00"))
    age_days = (datetime.now(timezone.utc) - created).days

    checks = {
        "account_age_ok": age_days >= min_age_days,
        "has_tweets": profile.get("tweets_count", 0) >= min_tweets,
        "has_followers": profile.get("followers_count", 0) >= min_followers,
        "not_protected": not profile.get("protected", False),
    }
    return all(checks.values()), checks

Three observations from running this in production:

  1. Minimum age of 30 days catches most fresh bot accounts. Bot farms typically register accounts in batches and use them within days. A 30-day floor knocks out the majority. Push to 90 days if your campaign is high-value.
  2. Zero-tweet accounts are almost always fake. A minimum of 5 to 10 existing tweets is a strong signal of real human use.
  3. Follower-to-following ratio matters less than you think. Real people with 50 followers and 800 following are common (passive consumers). Do not use ratio as a primary filter.

Apply this check before running any of the five verification checks. If is_legitimate_account returns False, you save 5 verification requests on a participant you would have rejected anyway.


Influence-Weighted Reward Scoring

Not all participants have the same reach. A retweet from a creator with 50,000 followers is worth more to a brand campaign than one from an account with 50. The straightforward fix is to weight each task's point value by a logarithmic function of the participant's follower count.

python
import math

BASE_POINTS = {"follow": 10, "retweet": 15, "quote": 25, "comment": 20, "community": 10}

def get_follower_count(username: str) -> int:
    resp = requests.get(
        f"{BASE}/info",
        headers={"ApiKey": API_KEY},
        params={"username": username},
        timeout=15,
    )
    resp.raise_for_status()
    return resp.json().get("followers_count", 0)


def calculate_weighted_points(result: ParticipantResult) -> dict:
    followers = get_follower_count(result.username)
    # log scaling: 100 followers -> 2x, 10K -> 4x, 1M -> 6x
    multiplier = max(1.0, math.log10(followers + 1))
    total = 0
    breakdown = {}
    for task, base in BASE_POINTS.items():
        if getattr(result, task):
            points = round(base * multiplier)
            breakdown[task] = points
            total += points
    return {"followers": followers, "multiplier": round(multiplier, 2),
            "breakdown": breakdown, "total": total}

The result: an account with 50 followers completing all five tasks earns about 80 points. An account with 50,000 followers completing the same tasks earns about 380 points. The campaign rewards reach proportionally without paying micro-celebrities the same as zero-reach accounts.

For crypto-flavored campaigns, you can replace the follower-count multiplier with Sorsa Score, which measures an account's recognition among crypto KOLs, projects, and VCs. Two accounts can have similar follower counts but very different Sorsa Scores if one is a crypto-native voice and the other is a general-interest account.


Case Study: 47,000 Participants in 14 Days

Last March, a creator-marketing agency I work with launched a 14-day giveaway for a direct-to-consumer home goods brand. The campaign mechanic was standard: follow the brand, retweet the launch tweet, quote it with a branded hashtag, comment on a second tweet, and join their X Community. Three winners would each receive a furnished room makeover valued at roughly $4,500.

Submissions came in via a campaign landing page. By day 14 they had 47,000 entries.

The agency's previous campaigns (run on honor-system checkboxes) typically saw 50 to 60 percent fake or partial completions, requiring days of manual review before announcing winners. This time they used the API verification pipeline above. Numbers from the run:

  • 47,000 total submissions
  • 235,000 verification requests (5 per participant)
  • 47,000 additional /info calls for the anti-fraud and influence-weight steps
  • Total API usage: ~282,000 requests over the campaign window
  • Plan used: Enterprise (500K requests/month at $899)
  • 31,200 participants passed all 5 tasks
  • 8,400 participants passed 3 to 4 tasks (eligible for partial-prize tier)
  • 7,400 participants rejected outright (failed account-quality check or completed 0 to 2 tasks)
  • Manual moderation time saved: ~120 hours (their estimate, based on past campaigns at similar scale)

The cost of running verification at this scale (one month of Enterprise) was less than a single day of moderator time. The agency now uses the same pipeline as a template for every brand campaign they run.

Disclosure: Sorsa API is our product. The figures above come from an anonymized client deployment. We have aimed to keep the technical claims accurate; for your own campaign, we recommend running a small pilot before committing to a workflow.


Cost Estimation per Participant

A full five-task verification, with anti-fraud and influence scoring layered on, takes 7 API requests per participant:

  • 1 request for /info (anti-fraud + follower count for scoring)
  • 5 requests for the five verification checks
  • 1 request optionally for account-ownership verification (if implemented)

Sorsa uses flat-rate pricing: 1 API call = 1 request from the monthly quota, regardless of endpoint. On the Pro plan ($199/mo, 100,000 requests), that is roughly 14,000 fully-verified participants per month. On Enterprise ($899/mo, 500,000 requests), about 71,000. Custom plans are available above that ceiling.

Campaign sizeRequests neededRecommended planPlan price
Up to 1,400 participants~10,000Starter$49/mo
Up to 14,000 participants~100,000Pro$199/mo
Up to 71,000 participants~500,000Enterprise$899/mo
71,000+ participantsCustomContact salesCustom

At Pro-plan rates, a full 7-request verification costs roughly $0.014 per participant. For a 10,000-participant campaign, that is approximately $140 in API costs.

For comparison, the official X API Pro plan costs $5,000/mo for 1M tweet reads, which is roughly 25x more expensive for comparable read volume on Sorsa Pro. Full per-plan and per-endpoint cost breakdown lives in our Twitter API pricing guide.


FAQ

Can I verify Twitter likes via API?

No. X made likes private in June 2024, and as of 2026 no public or third-party API can answer "did user A like tweet B." This is a platform-level change, not a Sorsa limitation. If your campaign template still asks for likes, replace that task with a retweet or comment. Both remain fully verifiable.

Do I need OAuth or developer-account approval to verify Twitter actions?

Not with Sorsa API. Authentication is a single ApiKey header. There is no OAuth handshake, no callback URLs, no app review. The official X API does require OAuth and an approved developer account, and the Pro tier ($5,000/mo) is the realistic starting point for verification at meaningful scale.

What is the rate limit for engagement verification?

Sorsa runs at 20 requests per second per API key, applied universally across all endpoints. With 5 requests per participant for a full check, one worker thread sequentially verifies about 4 participants per second, or roughly 14,000 per hour. Custom higher rate limits are available on request.

Can I check if someone retweeted a tweet that has thousands of retweets?

Yes. The /check-retweet endpoint scans 100 retweets per call and returns a next_cursor to paginate. The example code in this guide caps at 5 pages (500 retweets), which is usually enough because participants typically retweet within hours of being prompted. For tweets where you need to scan deeper, increase the page cap.

How do I verify someone actually owns the Twitter handle they entered?

Generate a unique short code, ask the participant to post a tweet containing it, then use the /user-tweets endpoint to scan their recent timeline for the code. Working example is in the Account Ownership section above. This is the standard pattern most serious giveaway platforms use to prevent submitted-handle spoofing.

What happens if a participant has a private (protected) account?

The /check-follow and /check-quoted responses include a user_protected flag. If true, the user's follow graph is not exposed and you cannot programmatically confirm whether they followed or quoted. Your options: ask them to make their profile public for verification, reject the entry with a clear message, or run account-ownership verification and accept the entry on a manual override. In a typical campaign, under 1% of participants have private accounts.

How much does verification cost per participant?

At Pro-plan rates, a full 7-request verification (anti-fraud + 5 task checks + ownership) costs roughly $0.014 per participant. For a 10,000-participant campaign, that is approximately $140 in API costs. See Cost Estimation above for plan-by-plan breakdown.

Can I run verification in real-time as participants submit?

Yes. Each check is a sub-second API call. For a real-time verification flow, run the 5 checks inline when the participant submits, return their result immediately, and re-run verification at campaign close to catch participants who completed tasks after the initial submission (a common pattern: the participant follows your brand after submitting and you give them a retry).

How do I prevent bots from gaming my giveaway?

Three layers of defense at the API level: (1) account quality check via /info for minimum age, minimum tweets, minimum followers; (2) comment and quote quality checks (minimum length, required hashtag, no emoji-only); (3) account ownership verification before final reward distribution. See Anti-Fraud for code. None of these is a complete sybil-detection solution, but together they remove the easy-win cases that drain most campaigns.

Is this faster than the official X API?

For verification specifically, yes, mostly because the official X API does not expose direct "did user X do action Y" endpoints. You have to fetch the full list of retweeters / quoters / repliers and search it yourself, which on the Pro tier ($5,000/mo) is affordable but architecturally heavier. Sorsa's verification endpoints return the boolean directly in one call.

Can I use these checks for non-marketing use cases?

Yes. Common non-marketing use cases include access-gating private Discord channels (verify the user follows the brand before granting Discord role), tracking employee or partner social-amplification compliance, and validating user-submitted attribution claims in affiliate programs.

Last verified: May 18, 2026.