Skip to main content
How to Verify Twitter Engagement Actions via API: Follows, Retweets, Comments, Quotes Running a social media campaign where users earn rewards for completing actions on X (formerly Twitter) - follow an account, retweet a post, leave a comment, join a community - requires a way to verify that each participant actually did what they claimed. Doing this manually does not scale past a handful of users. Doing it with honor-system checkboxes invites bots and fraud. Sorsa API provides a dedicated set of verification endpoints that answer simple yes/no questions: did this user follow that account? Did they retweet this tweet? Did they comment on it? Did they join this community? Each check is a single API call that returns a boolean result, making it straightforward to build automated quest systems, giveaway platforms, referral programs, and engagement campaigns with provable, on-chain-style verification. This guide covers every available verification endpoint with working code, then shows how to combine them into a complete campaign verification pipeline.

Available Verification Checks

Here is what you can verify, which endpoint to use, and what you cannot check:
ActionEndpointMethodWhat it returns
User follows an account/check-followPOST{follow: true/false}
User retweeted a tweet/check-retweetPOST{retweet: true/false}
User quoted a tweet/check-quotedPOST{status: "quoted" / "retweet" / "not_found"}
User commented on a tweet/check-commentGET{commented: true/false}
User is a community member/check-community-memberPOST{is_member: true/false}
What you cannot verify: Likes. X made likes private in 2024, so no API (including the official one) can check whether a specific user liked a specific tweet. Design your campaigns around the five actions above.

Check 1: Did the User Follow an Account?

The most common campaign task. “Follow @YourBrand to enter the giveaway.” Endpoint: POST /v3/check-follow

Simplest Example

curl -X POST https://api.sorsa.io/v3/check-follow \
  -H "ApiKey: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "project_handle": "YourBrand",
    "username": "participant_handle"
  }'
Response:
{
  "follow": true,
  "user_protected": false
}

Parameters

ParameterTypeRequiredDescription
project_handlestringOne ofThe account that should be followed (your brand).
project_idstringtheseAlternatively, the numeric user ID of the target account.
usernamestringOne ofThe participant’s handle.
user_linkstringtheseOr their profile URL.
user_idstringOr their numeric user ID.

Python

import requests

API_KEY = "YOUR_API_KEY"

def check_follow(brand_handle, participant_handle):
    resp = requests.post(
        "https://api.sorsa.io/v3/check-follow",
        headers={"ApiKey": API_KEY, "Content-Type": "application/json"},
        json={
            "project_handle": brand_handle,
            "username": participant_handle,
        },
    )
    resp.raise_for_status()
    return resp.json()


result = check_follow("YourBrand", "participant123")
if result.get("follow"):
    print("Follow verified.")
else:
    print("User has not followed yet.")

JavaScript

async function checkFollow(brandHandle, participantHandle) {
  const resp = await fetch("https://api.sorsa.io/v3/check-follow", {
    method: "POST",
    headers: { "ApiKey": "YOUR_API_KEY", "Content-Type": "application/json" },
    body: JSON.stringify({
      project_handle: brandHandle,
      username: participantHandle,
    }),
  });
  return resp.json();
}

const result = await checkFollow("YourBrand", "participant123");
console.log(result.follow ? "Follow verified" : "Not following");
Note: if user_protected is true, the user’s follow relationships are private and cannot be verified.

Check 2: Did the User Retweet a Tweet?

“Retweet this post to enter.” The endpoint checks up to 100 retweets per request and supports pagination for tweets with thousands of retweets. Endpoint: POST /v3/check-retweet

Simplest Example

def check_retweet(tweet_link, participant_handle):
    resp = requests.post(
        "https://api.sorsa.io/v3/check-retweet",
        headers={"ApiKey": API_KEY, "Content-Type": "application/json"},
        json={
            "tweet_link": tweet_link,
            "username": participant_handle,
        },
    )
    resp.raise_for_status()
    return resp.json()


result = check_retweet("https://x.com/YourBrand/status/1234567890", "participant123")
if result.get("retweet"):
    print("Retweet verified.")
else:
    print("Retweet not found.")

Parameters

ParameterTypeRequiredDescription
tweet_linkstringYesFull URL of the tweet that should be retweeted.
usernamestringOne ofParticipant’s handle.
user_linkstringtheseOr profile URL.
user_idstringOr numeric user ID.
next_cursorstringNoFor pagination if checking tweets with many retweets.
The endpoint checks up to 100 retweets per request. If the tweet has more than 100 retweets and the user is not found in the first batch, the response includes a next_cursor you can use to check the next batch. For most campaign verification, one request is sufficient - users typically retweet shortly after the campaign starts, and their retweet will be in the most recent batch.

Check 3: Did the User Quote a Tweet?

“Quote tweet this post with your thoughts.” The /check-quoted endpoint distinguishes between a quote tweet and a plain retweet, returning a status string. Endpoint: POST /v3/check-quoted

Simplest Example

def check_quoted(tweet_link, participant_handle):
    resp = requests.post(
        "https://api.sorsa.io/v3/check-quoted",
        headers={"ApiKey": API_KEY, "Content-Type": "application/json"},
        json={
            "tweet_link": tweet_link,
            "username": participant_handle,
        },
    )
    resp.raise_for_status()
    return resp.json()


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

if result["status"] == "quoted":
    print(f"Quote verified! They wrote: {result['text']}")
elif result["status"] == "retweet":
    print("They retweeted but did not quote.")
else:
    print("No quote or retweet found.")

Response

{
  "status": "quoted",
  "date": "2026-03-10 14:22:09",
  "text": "This is amazing, everyone should check this out!",
  "user_protected": false
}
The status field returns one of three values: "quoted" (user posted a quote tweet), "retweet" (user retweeted without adding text), or "not_found" (neither action detected). The response also includes the date and text of the quote, which you can use for content quality checks.

Check 4: Did the User Comment on a Tweet?

“Leave a comment under this post.” This is the only verification endpoint that uses GET instead of POST. Endpoint: GET /v3/check-comment

Simplest Example

def check_comment(tweet_link, participant_handle):
    resp = requests.get(
        "https://api.sorsa.io/v3/check-comment",
        headers={"ApiKey": API_KEY},
        params={
            "tweet_link": tweet_link,
            "user_handle": participant_handle,
        },
    )
    resp.raise_for_status()
    return resp.json()


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

if result.get("commented"):
    print(f"Comment verified: {result['tweet']['full_text'][:100]}")
else:
    print("No comment found.")

Parameters (query string)

ParameterTypeRequiredDescription
tweet_linkstringYesURL of the tweet to check for comments.
user_handlestringOne ofParticipant’s handle.
user_idstringtheseOr numeric user ID.
When commented is true, the response includes the full tweet object of the comment itself - with text, engagement metrics, and timestamp. You can use this to verify comment quality (e.g., minimum length, must contain a specific hashtag) beyond just checking existence.

Check 5: Is the User a Community Member?

“Join our X Community to participate.” Useful for campaigns that require community membership as a prerequisite. Endpoint: POST /v3/check-community-member

Simplest Example

def check_community_member(community_id, participant_handle):
    resp = requests.post(
        "https://api.sorsa.io/v3/check-community-member",
        headers={"ApiKey": API_KEY, "Content-Type": "application/json"},
        json={
            "community_id": community_id,
            "username": participant_handle,
        },
    )
    resp.raise_for_status()
    return resp.json()


result = check_community_member("1966045657589813686", "participant123")
if result.get("is_member"):
    print("Community membership verified.")
else:
    print("User is not a member of this community.")

Building a Campaign Verification Pipeline

In a real campaign, users complete multiple tasks. Here is a pattern that checks all tasks for a single participant and returns which ones they have completed:
import requests

API_KEY = "YOUR_API_KEY"
HEADERS = {"ApiKey": API_KEY, "Content-Type": "application/json"}

# Campaign configuration
CAMPAIGN = {
    "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",
}


def verify_participant(username):
    """Check all campaign tasks for a single participant. Returns a dict of results."""
    results = {}

    # Task 1: Follow
    resp = requests.post(
        "https://api.sorsa.io/v3/check-follow",
        headers=HEADERS,
        json={"project_handle": CAMPAIGN["brand_handle"], "username": username},
    )
    results["follow"] = resp.json().get("follow", False)

    # Task 2: Retweet
    resp = requests.post(
        "https://api.sorsa.io/v3/check-retweet",
        headers=HEADERS,
        json={"tweet_link": CAMPAIGN["tweet_to_retweet"], "username": username},
    )
    results["retweet"] = resp.json().get("retweet", False)

    # Task 3: Quote tweet
    resp = requests.post(
        "https://api.sorsa.io/v3/check-quoted",
        headers=HEADERS,
        json={"tweet_link": CAMPAIGN["tweet_to_quote"], "username": username},
    )
    quote_data = resp.json()
    results["quote"] = quote_data.get("status") == "quoted"
    results["quote_text"] = quote_data.get("text", "")

    # Task 4: Comment
    resp = requests.get(
        "https://api.sorsa.io/v3/check-comment",
        headers={"ApiKey": API_KEY},
        params={"tweet_link": CAMPAIGN["tweet_to_comment"], "user_handle": username},
    )
    comment_data = resp.json()
    results["comment"] = comment_data.get("commented", False)

    # Task 5: Community membership
    resp = requests.post(
        "https://api.sorsa.io/v3/check-community-member",
        headers=HEADERS,
        json={"community_id": CAMPAIGN["community_id"], "username": username},
    )
    results["community"] = resp.json().get("is_member", False)

    return results


# Verify a participant
result = verify_participant("participant123")

completed = sum(1 for v in result.values() if v and v is not True or v is True)
tasks = ["follow", "retweet", "quote", "comment", "community"]
total = len(tasks)
done = sum(1 for t in tasks if result.get(t))

print(f"@participant123: {done}/{total} tasks completed")
for task in tasks:
    status = "done" if result.get(task) else "pending"
    print(f"  {task}: {status}")

Verifying Participants in Bulk

When a campaign has hundreds or thousands of participants, you need to verify them in batch. Here is a pattern that processes a list of usernames and outputs a summary:
import time
import csv

def verify_campaign_batch(usernames, output_file="campaign_results.csv"):
    """Verify all tasks for a list of participants and export results."""
    tasks = ["follow", "retweet", "quote", "comment", "community"]

    with open(output_file, "w", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=["username"] + tasks + ["total_done"])
        writer.writeheader()

        for i, username in enumerate(usernames):
            result = verify_participant(username)
            done = sum(1 for t in tasks if result.get(t))

            row = {"username": username, "total_done": done}
            for t in tasks:
                row[t] = result.get(t, False)
            writer.writerow(row)

            print(f"[{i+1}/{len(usernames)}] @{username}: {done}/{len(tasks)} tasks")
            time.sleep(0.3)  # 5 API calls per participant, stay within rate limits

    print(f"\nResults exported to {output_file}")


participants = ["user1", "user2", "user3", "user4", "user5"]
verify_campaign_batch(participants, "giveaway_results.csv")
Each participant requires 5 API calls (one per task). At Sorsa’s 20 req/s rate limit, you can verify about 4 participants per second, or roughly 14,000 per hour. The time.sleep(0.3) in the example above keeps you safely within limits.

Account Ownership Verification

Before a user can participate in your campaign, you may want to prove they actually own the X handle they provided. A common pattern:
  1. Generate a unique code (e.g., VERIFY-a8f3b2) and show it to the user.
  2. Ask them to post a tweet containing that code.
  3. Use /user-tweets to fetch their recent tweets and check if the code appears.
import secrets

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


def verify_account_ownership(username, expected_code):
    """Check if the user posted a tweet containing the verification code."""
    resp = requests.post(
        "https://api.sorsa.io/v3/user-tweets",
        headers=HEADERS,
        json={"link": f"https://x.com/{username}"},
    )
    resp.raise_for_status()
    tweets = resp.json().get("tweets", [])

    for tweet in tweets:
        if expected_code in tweet["full_text"]:
            return True
    return False


code = generate_verification_code()
print(f"Ask the user to tweet: {code}")
# ... after user tweets ...
if verify_account_ownership("participant123", code):
    print("Account ownership confirmed.")

Scoring Participants by Influence

Not all participants have equal reach. A retweet from an account with 50,000 followers is worth more to your campaign than one from an account with 50 followers. Use the /info endpoint to fetch the participant’s profile and weight their reward accordingly:
import math

def get_influence_score(username):
    """Fetch a user's follower count and compute an influence multiplier."""
    resp = requests.get(
        "https://api.sorsa.io/v3/info",
        headers={"ApiKey": API_KEY},
        params={"username": username},
    )
    resp.raise_for_status()
    followers = resp.json().get("followers_count", 0)

    # Logarithmic scaling: 100 followers = 2x, 10K = 4x, 1M = 6x
    multiplier = max(1, math.log10(followers + 1))
    return followers, round(multiplier, 2)


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

def calculate_rewards(username, completed_tasks):
    """Calculate weighted points based on completed tasks and influence."""
    followers, multiplier = get_influence_score(username)
    total = 0

    for task, done in completed_tasks.items():
        if done and task in BASE_POINTS:
            points = int(BASE_POINTS[task] * multiplier)
            total += points

    return {"username": username, "followers": followers, "multiplier": multiplier, "points": total}


# Example
tasks = {"follow": True, "retweet": True, "quote": False, "comment": True, "community": True}
reward = calculate_rewards("participant123", tasks)
print(f"@{reward['username']}: {reward['points']} points (x{reward['multiplier']} multiplier, {reward['followers']} followers)")

Anti-Fraud Considerations

Automated campaigns attract bots. A few checks to keep your campaign clean: Minimum account age. Fetch the participant’s profile via /info and check created_at. Reject accounts created in the last 30 days - most bot farms use fresh accounts. Minimum activity. Check tweets_count and followers_count. An account with 0 tweets and 2 followers is almost certainly not a real participant. Comment quality. When verifying comments via /check-comment, the response includes the full tweet text. Check for minimum length, presence of required keywords or hashtags, and reject single-character or emoji-only replies. Quote quality. The /check-quoted response includes the quote text. Apply the same quality checks as for comments. Rate of completion. If a user completes all 5 tasks within 3 seconds of receiving the task list, that is a bot. Log timestamps and flag suspiciously fast completions.
def is_legitimate_account(username, min_age_days=30, min_tweets=10, min_followers=5):
    """Basic anti-fraud check on a participant's account."""
    resp = requests.get(
        "https://api.sorsa.io/v3/info",
        headers={"ApiKey": API_KEY},
        params={"username": username},
    )
    resp.raise_for_status()
    profile = resp.json()

    from datetime import datetime, timezone

    # Parse account creation date
    created = datetime.strptime(profile["created_at"], "%a %b %d %H:%M:%S %z %Y")
    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", True),
    }

    return all(checks.values()), checks

A Note on Likes

X (Twitter) made likes private in 2024. The platform no longer exposes which users liked a specific tweet through any public API - not Sorsa, not the official X API, not any third-party tool. If your campaign previously included a “Like this tweet” task, replace it with a retweet or comment requirement, both of which remain fully verifiable.

Next Steps

  • How to Search Tweets via API - find campaign-related tweets by keyword for broader monitoring.
  • Search Mentions Guide - track organic mentions of your brand alongside campaign-driven mentions.
  • Real-Time Monitoring - verify tasks in near-real-time by polling for new activity.
  • Followers and Following - extract your own follower list to cross-reference with campaign participants.
  • API Reference - full specification for /check-follow, /check-retweet, /check-quoted, /check-comment, /check-community-member, and all 38 endpoints.