Telegram Got This Simple UI Pattern Wrong

Do you know the difference between radio buttons and checkboxes?

I recently ran into a few misunderstandings while using polls in Telegram with multiple selections enabled. The poll shows radio buttons even though users can select multiple options.

By default:

  • Radio buttons signal single choice
  • Checkboxes signal multiple choices

So when users see radio buttons, they think they can pick only one option. And that’s exactly leading to unexpected results if you create a multiple selection pool.

Users trust visual patterns more than instructions. When UI elements don’t match their expected behavior, misunderstandings are inevitable.

Clarity and consistency in UI are not optional — they directly affect how people think and act.

UI #Frontend #Telegram

Prompt Unit Tests: 3 Bash Scripts That Catch Regressions Before Deploy

You changed one line in your system prompt and broke three downstream features. No tests caught it because — let’s be honest — you don’t test your prompts.

Here are three dead-simple bash scripts I use to catch prompt regressions before they hit production.

Script 1: The Golden Output Test

This script sends a fixed input to your prompt and diffs the output against a known-good response.

#!/bin/bash
# test-golden.sh — Compare prompt output against golden file

PROMPT_FILE="$1"
INPUT_FILE="$2"
GOLDEN_FILE="$3"

ACTUAL=$(cat "$PROMPT_FILE" "$INPUT_FILE" | 
  curl -s https://api.openai.com/v1/chat/completions 
    -H "Authorization: Bearer $OPENAI_API_KEY" 
    -H "Content-Type: application/json" 
    -d @- <<EOF
{
  "model": "gpt-4o-mini",
  "messages": [
    {"role": "system", "content": "$(cat $PROMPT_FILE)"},
    {"role": "user", "content": "$(cat $INPUT_FILE)"}
  ],
  "temperature": 0
}
EOF
  | jq -r '.choices[0].message.content')

echo "$ACTUAL" > /tmp/prompt-test-actual.txt

if diff -q "$GOLDEN_FILE" /tmp/prompt-test-actual.txt > /dev/null 2>&1; then
  echo "✅ PASS: Output matches golden file"
else
  echo "❌ FAIL: Output diverged"
  diff --color "$GOLDEN_FILE" /tmp/prompt-test-actual.txt
  exit 1
fi

Usage:

./test-golden.sh prompts/summarize.txt fixtures/input-1.txt fixtures/expected-1.txt

When to use: After any prompt edit. Set temperature: 0 for deterministic output. Update the golden file intentionally when you want the output to change.

Script 2: The Keyword Gate

Sometimes you don’t need exact match — you just need the output to contain (or NOT contain) specific terms.

#!/bin/bash
# test-keywords.sh — Assert required/forbidden keywords in output

PROMPT_FILE="$1"
INPUT_FILE="$2"
REQUIRED="$3"   # comma-separated: "function,return,async"
FORBIDDEN="$4"  # comma-separated: "TODO,FIXME,undefined"

ACTUAL=$(curl -s https://api.openai.com/v1/chat/completions 
  -H "Authorization: Bearer $OPENAI_API_KEY" 
  -H "Content-Type: application/json" 
  -d "{
    "model": "gpt-4o-mini",
    "messages": [
      {"role": "system", "content": $(jq -Rs . < $PROMPT_FILE)},
      {"role": "user", "content": $(jq -Rs . < $INPUT_FILE)}
    ],
    "temperature": 0
  }" | jq -r '.choices[0].message.content')

PASS=true

IFS=',' read -ra REQ <<< "$REQUIRED"
for keyword in "${REQ[@]}"; do
  if ! echo "$ACTUAL" | grep -qi "$keyword"; then
    echo "❌ Missing required keyword: $keyword"
    PASS=false
  fi
done

IFS=',' read -ra FORB <<< "$FORBIDDEN"
for keyword in "${FORB[@]}"; do
  if echo "$ACTUAL" | grep -qi "$keyword"; then
    echo "❌ Found forbidden keyword: $keyword"
    PASS=false
  fi
done

if $PASS; then
  echo "✅ PASS: All keyword checks passed"
else
  echo "--- Actual output ---"
  echo "$ACTUAL"
  exit 1
fi

Usage:

./test-keywords.sh prompts/code-review.txt fixtures/pr-diff.txt 
  "security,error handling" "looks good,LGTM"

Script 3: The Format Validator

For structured output (JSON, YAML, specific sections), validate the shape — not the content.

#!/bin/bash
# test-format.sh — Validate output structure

PROMPT_FILE="$1"
INPUT_FILE="$2"
FORMAT="$3"  # "json" | "has-headers" | "max-lines:N"

ACTUAL=$(curl -s https://api.openai.com/v1/chat/completions 
  -H "Authorization: Bearer $OPENAI_API_KEY" 
  -H "Content-Type: application/json" 
  -d "{
    "model": "gpt-4o-mini",
    "messages": [
      {"role": "system", "content": $(jq -Rs . < $PROMPT_FILE)},
      {"role": "user", "content": $(jq -Rs . < $INPUT_FILE)}
    ],
    "temperature": 0
  }" | jq -r '.choices[0].message.content')

case "$FORMAT" in
  json)
    if echo "$ACTUAL" | jq . > /dev/null 2>&1; then
      echo "✅ PASS: Valid JSON"
    else
      echo "❌ FAIL: Invalid JSON"
      echo "$ACTUAL"
      exit 1
    fi
    ;;
  has-headers)
    if echo "$ACTUAL" | grep -q "^#"; then
      echo "✅ PASS: Contains markdown headers"
    else
      echo "❌ FAIL: No markdown headers found"
      exit 1
    fi
    ;;
  max-lines:*)
    MAX="${FORMAT#max-lines:}"
    LINES=$(echo "$ACTUAL" | wc -l)
    if [ "$LINES" -le "$MAX" ]; then
      echo "✅ PASS: $LINES lines (max: $MAX)"
    else
      echo "❌ FAIL: $LINES lines exceeds max $MAX"
      exit 1
    fi
    ;;
esac

Putting It Together

I run all three in a Makefile:

test-prompts:
    ./test-golden.sh prompts/summarize.txt fixtures/doc-1.txt fixtures/expected-summary-1.txt
    ./test-keywords.sh prompts/review.txt fixtures/pr-1.txt "security,performance" "LGTM"
    ./test-format.sh prompts/extract.txt fixtures/email-1.txt json

Hook it into CI:

# .github/workflows/prompt-tests.yml
on:
  push:
    paths: ['prompts/**']
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: make test-prompts
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}

Now every prompt change gets tested automatically. Total setup time: 20 minutes. Regressions caught since I started: seven.

Your prompts are code. Test them like it.

We Stopped Trusting Uptime Metrics. Here’s What We Monitor Instead.

We’re going to make a claim that might sound controversial:

Uptime monitoring and website monitoring are not the same thing. Most people use the terms interchangeably. They shouldn’t.

Uptime monitoring answers: “Did the server respond?”
Website monitoring answers: “Does the site work?”

Those sound similar. They’re not. And the gap between them is where most silent failures live.

What uptime monitoring actually does

Let’s be specific about what a typical uptime monitoring tool checks. There’s no ambiguity here — this is the standard model that tools like UptimeRobot, Pingdom, Better Stack, and most others follow:

  1. Send an HTTP request to your URL
  2. Receive a response
  3. Check the status code
  4. If it’s 200 OK → mark as healthy
  5. If it’s 5xx or timeout → mark as down
  6. Report uptime percentage

That’s it. That’s the check.

It tells you whether your server is alive and responding to requests. It does that well. And for a long time, that was enough — because if the server was up, the site worked.

That’s no longer true.

What uptime monitoring does NOT check

Here’s a non-exhaustive list of things a standard uptime check will miss:

Asset integrity. Your HTML loads, but the JavaScript bundle it references returns 404 because you deployed and the CDN still serves old HTML pointing to deleted files. The page is a blank white screen. Status code on the document: 200.

MIME types. Your server returns a JS file with Content-Type: text/html. The browser downloads it, reads the header, silently refuses to execute it. Your SPA never boots. Status: 200.

Redirect chains. Your site redirects /page → /page/ → /page → /page/. The browser gives up after 20 hops. Your uptime tool followed the first redirect, got a 200, and moved on.

Content correctness. Someone changed your homepage in the CMS at 2am. Or your CDN started serving content from the wrong origin after a migration. The page loads — it’s just the wrong page. Status: 200.

Third-party dependencies. Stripe’s JS fails to load. Your checkout renders as an empty div. Your auth provider is down. Users can’t log in. Your page loaded fine — the broken resource is on someone else’s domain.

Regional divergence. Your site works from US-East. In Frankfurt, the CDN edge serves stale cached assets from three deploys ago. Single-region uptime checks never see this.

Every single one of these returns 200 OK. Every single one passes a standard uptime check. Every single one is broken for users.

What website monitoring checks instead

Website monitoring starts where uptime monitoring stops. Instead of asking “did the server respond?”, it asks “did the response constitute a working page?”

That means:

Treating the HTML document as a manifest, not a destination. The document is step one. Website monitoring follows up — do the JS and CSS bundles it references actually load? Do they return the right MIME types? Do they return valid status codes?

Following redirect chains to completion. Not just the first hop. The full chain. Does it resolve? Does it loop? Does it end somewhere unexpected?

Fingerprinting content over time. If the response body changes without a deployment, you want to know. Content fingerprinting with SHA-256 hashes catches silent CMS edits, CDN origin drift, and cache poisoning.

Checking from multiple regions. CDN failures are regional by nature. A site can be perfect in one location and broken in another. Website monitoring runs the full check suite from multiple geographic locations independently.

Classifying root cause. Not just “something is wrong” but “why.” Is this a deployment artifact? A CDN cache issue? A DNS drift? An application error? Root cause classification turns an alert into actionable information.

A practical comparison

Here’s the same scenario through both lenses:

Scenario: You deploy a Nuxt 3 app to Vercel. The build succeeds. But a CDN edge node in Europe still serves the previous HTML, which references app.7fb2e.js. That file was deleted in the new build.

Uptime monitoring Website monitoring
HTTP status 200 OK ✅ 200 OK (document)
Asset check Not checked app.7fb2e.js → 404 ❌
MIME validation Not checked N/A (asset missing)
Multi-region Single region ✅ EU: broken ❌, US: ok ✅
Alert None Incident: deployment artifact missing, EU region
Root cause N/A CDN cache stale, Vercel ISR
User impact Unknown European users see blank page

Same site. Same moment. Completely different picture.

They’re complementary, not competing

We want to be clear about something: we’re not saying uptime monitoring is useless. It’s not. You absolutely need to know when your server is down. A 503 or a timeout is a real outage and you need to know about it immediately.

The problem is treating uptime monitoring as sufficient. It’s one layer. It catches one type of failure. The failures it misses — the “200 OK but broken” failures — are increasingly the ones that actually hurt users.

The monitoring stack for a modern web application should have both:

Uptime monitoring → Is the server alive? Is it responding? Is the infrastructure running?

Website monitoring → Does the site work? Are assets loading? Is the content correct? Does it work everywhere?

If you only have the first, you have a blind spot. And it’s the kind of blind spot where your dashboard says 99.9% uptime while your users are staring at blank pages.

This is why we built Sitewatch

Sitewatch is website monitoring. Not uptime monitoring. The distinction matters to us because it’s the entire reason the product exists.

We check asset integrity, MIME types, redirect chains, content fingerprints, and multi-region consistency. When something breaks, we classify root cause — infrastructure, application, or content delivery — and provide fix guidance tailored to your tech stack.

Every check runs a 2-of-3 retry confirmation model so you don’t get woken up at 3am for a transient CDN hiccup. Duplicate alerts are deduplicated with SHA-256 fingerprinting and a 30-minute cooldown.

Free tier for 1 site. No credit card. Takes 30 seconds: getsitewatch.com

Uptime monitoring tells you the server is alive. Website monitoring tells you the site works. They’re not the same question — and the answer to one doesn’t imply the answer to the other.

Someone Backdoored axios on npm. Here is How to Check if You Were Hit

On March 31, 2026, two malicious versions of axios were published to npm: [email protected] and [email protected]. Both were live for roughly three hours before npm pulled them down. During that window, anyone who ran npm install axios could have had a Remote Access Trojan (RAT) dropped silently on their machine or CI runner, with no errors and no warnings.

This post breaks down what happened, how the attack worked, and the exact commands to check if you were affected.

What happened

The attacker compromised the npm account of the primary axios maintainer. Using stolen credentials, they published two new releases across both the 1.x and 0.x branches within 39 minutes of each other. The account’s registered email was quietly changed to an attacker-controlled ProtonMail address before the releases went out.

Here is what makes this attack stand out: there is zero malicious code inside axios itself. Both releases simply added one new runtime dependency to package.json: a package called [email protected], which had never appeared in any legitimate axios version.

When you ran npm install, npm resolved and installed that dependency automatically, which triggered its postinstall script and silently executed the dropper.

The malicious dependency: plain-crypto-js

[email protected] was staged 18 hours before the axios releases, by a separate attacker-controlled account. It masquerades as a clone of the legitimate crypto-js library, with an identical description and the real author’s name in the manifest. Visually, it looks harmless. The only difference from the real package is a single extra field in package.json:

"scripts": {
  "test": "grunt",
  "postinstall": "node setup.js"
}

That setup.js is a cross-platform RAT dropper.

What setup.js does

The dropper detects your operating system and then:

  • macOS: writes an AppleScript to /tmp, executes it via nohup osascript, downloads a binary to /Library/Caches/com.apple.act.mond (named to blend in with Apple system processes), makes it executable and launches it
  • Linux: fetches a Python script from the C2 server to /tmp/ld.py and runs it in the background
  • Windows: copies PowerShell to %PROGRAMDATA%wt.exe (disguised as Windows Terminal), then uses a VBScript to silently fetch and execute a PowerShell payload

All of this runs in under two seconds from the moment npm install starts. The npm install itself exits with code 0 and shows no errors.

The anti-forensics layer

After launching the payload, the dropper:

  1. Deletes setup.js from node_modules/plain-crypto-js/
  2. Deletes the malicious package.json
  3. Renames a pre-staged clean stub (package.md) to package.json, which reports version 4.2.0 with no scripts

After the swap, running npm audit reveals nothing. Running npm list plain-crypto-js shows version 4.2.0. The only reliable on-disk evidence is the existence of the node_modules/plain-crypto-js/ directory, since this package was never a dependency of any real axios release.

A note on the publish metadata

Every legitimate axios 1.x release is published via GitHub Actions with npm’s OIDC Trusted Publisher mechanism, meaning the publish is cryptographically tied to a verified workflow. [email protected] breaks that pattern entirely. There is no corresponding tag or commit in the axios GitHub repository. It was published manually using a stolen npm access token.

Am I affected?

The malicious versions were live between approximately 00:21 UTC and 03:15 UTC on March 31, 2026. If you did not run npm install (or a fresh npm ci) during that window, you were likely not hit.

Run through these steps to verify.

Step 1: Check for the malicious axios versions

npm list axios 2>/dev/null | grep -E "1.14.1|0.30.4"
grep -A1 '"axios"' package-lock.json | grep -E "1.14.1|0.30.4"

No output means you are probably fine. Any output means those versions were installed.

Step 2: Check for plain-crypto-js in node_modules

ls node_modules/plain-crypto-js 2>/dev/null && echo "POTENTIALLY AFFECTED"

If the directory exists, the dropper ran. Do not rely on the version number inside it.

Step 3: Check for RAT artifacts on the system

macOS

ls -la /Library/Caches/com.apple.act.mond 2>/dev/null && echo "COMPROMISED"

Linux

ls -la /tmp/ld.py 2>/dev/null && echo "COMPROMISED"

Windows (cmd.exe)

dir "%PROGRAMDATA%wt.exe" 2>nul && echo COMPROMISED

Step 4: Check for C2 activity in logs

grep -r "sfrclak.com" ~/.npm/_logs/ 2>/dev/null
grep "sfrclak" ~/.bash_history ~/.zsh_history 2>/dev/null

Step 5: Check your CI/CD pipelines

Review any npm install runs in GitHub Actions or other CI systems between 00:20 UTC and 03:15 UTC on March 31. Any pipeline that ran with those axios versions should be treated as compromised, and all secrets injected in that workflow should be rotated immediately.

If you got multiple projects running

for dir in ~/projects/*/; do
  if [ -f "$dir/package.json" ]; then
    result=""
    if ls "$dir/node_modules/plain-crypto-js" 2>/dev/null | grep -q .; then
      result="DROPPER FOUND"
    fi
    axios_hit=$(grep -A1 '"axios"' "$dir/package-lock.json" 2>/dev/null | grep -E "1.14.1|0.30.4")
    if [ -n "$axios_hit" ]; then
      result="$result | MALICIOUS AXIOS VERSION"
    fi
    if [ -n "$result" ]; then
      echo "⚠️  $dir → $result"
    else
      echo "✅  $dir"
    fi
  fi
done

Remediation

1. Downgrade to a safe version

# 1.x users
npm install [email protected]

# 0.x users
npm install [email protected]

2. Pin the version in your package.json

{
  "overrides":   { "axios": "1.14.0" },
  "resolutions": { "axios": "1.14.0" }
}

3. Remove plain-crypto-js and reinstall

rm -rf node_modules/plain-crypto-js
npm install --ignore-scripts

4. Block the C2 domain (macOS / Linux)

echo "0.0.0.0 sfrclak.com" | sudo tee -a /etc/hosts
sudo iptables -A OUTPUT -d 142.11.206.73 -j DROP

5. Add –ignore-scripts to your CI installs going forward

npm ci --ignore-scripts

This prevents any postinstall hook from running during automated builds, which would have stopped this attack entirely.

If you found a RAT artifact

Do not try to clean the machine in place. Rebuild from a known-good state and rotate everything that was accessible during the compromised install: npm tokens, AWS access keys, SSH private keys, cloud credentials, CI/CD secrets, and any .env values present at install time.

Indicators of Compromise

Category Value
Malicious packages [email protected], [email protected], [email protected]
C2 domain sfrclak.com
C2 IP 142.11.206.73
C2 port 8000
macOS artifact /Library/Caches/com.apple.act.mond
Linux artifact /tmp/ld.py
Windows artifact %PROGRAMDATA%wt.exe
Safe 1.x version [email protected] (shasum 7c29f4cf2ea91ef05018d5aa5399bf23ed3120eb)

Takeaways

This attack did not require any vulnerability in axios itself. It exploited the trust developers place in a well-known package name and a familiar maintainer account. The entire weapon was a single extra line in package.json.

A few habits that would have reduced exposure here:

  • Run npm ci --ignore-scripts in CI pipelines as a standing policy
  • Use npm’s Trusted Publisher provenance statements to verify releases were published from the expected CI workflow
  • Watch for new dependencies appearing in transitive installs that were not there before
  • Pin your lockfile and review package-lock.json diffs in pull requests

The full technical breakdown, including the decoded dropper, process tree, and file swap events captured at runtime, is in the StepSecurity report.

Stay safe.

Creem CLI : Powerful Developer Tool

creem cli version

When working with payments, developers waste time context-switching between dashboards, logs, terminals, and code editors. This reduces the productivity of developer and also slows down development. Creem CLI helps you to manage your entire Creem store from the terminal. Everything from managing products, customers, subscription and even the migration.

And when you combine it with AI coding assistants like Claude Code, Cursor, or Windsurf, it becomes even more powerful. Instead of memorizing commands, you can just describe what you want and get things done faster.

In this article, we’ll set up Creem CLI, integrate it with AI coding tools, and explore how it can streamline your workflow and make development easier.

Setup Creem

Install Creem CLI

brew tap armitage-labs/creem
brew install creem

Once you install the CLI, you can verify installation with the below command:

creem --version

creem cli version

Authentication

Once setup is done, you need to authenticate yourself to perform operations on your store. You will need API key to authenticate. You can find this from your creem dashboard.

creem login --api-key creem_test_YOUR_KEY_HERE

once done, you can also verify your account with below command:

creem whoami

creem user verification

Now you are authenticated and can perform all kind of action to your creem account with Creem CLI.

Overview

Now, lets see some of the basic and most useful CLI commands to get you the better idea of how CLI can be helpful.

# list all the products
creem products

# list all the customers of store
creem customers

# list all the subscriptions of store
creem subscriptions

# list all the transactions of store
creem transactions

Browse all the Products

Run the command Creem Products to list all the products from our store.

creem products command results
You can navigate and click enter on any product to get the product details. Here I have selected one product and below is the details of it

creem selected product

likewise, you can also browse all your transactions, customers and Subscriptions.

Manage Subscriptions

You can directly manage the subscriptions from the CLI, without ever login in to your creem dashboard.

Manage subscription

detailed subscription

Check the status bar for commands to update the subscription status.

Quick commands

Here is the list of commands you will mostly use.

# Products
creem products list
creem products get prod_XXXXX
creem products create --name "Pro Plan" --price 1999 --currency USD --billing-type recurring --billing-period every-month

# Customers
creem customers list
creem customers get cust_XXXXX
creem customers billing cust_XXXXX   # generates a billing portal link

# Subscriptions
creem subscriptions list --status active
creem subscriptions cancel sub_XXXXX --mode scheduled
creem subscriptions pause sub_XXXXX
creem subscriptions resume sub_XXXXX

# Transactions
creem transactions list --limit 50
creem transactions get txn_XXXXX

# Checkouts
creem checkouts create --product prod_XXXXX --success-url https://app.com/welcome

How to Use CLI Most effectively:

For any details regarding any command in creem CLI, you can always use creem -h or creem --help. For example if you don’t know what are the options for the command for creating product from Creem CLI then I will fire the command like
creem products create —help

and it will instantly give me all the available options:

Creem help options

and by using this we can easily create product as per our requirement.

Now we have got the basic idea about what Creem CLI is and how can we use it. Now, we will see how we can see how we can use this in AI coding tool like Claude Code.

Integrate Creem CLI with Coding tool

For this purpose, I created a simple Next.js application with two products: one one-time product and one subscription product. There is also a success page with a link to the customer portal for managing payments. Additionally, we have Orders and Subscriptions pages where all orders and subscriptions are displayed.

For this project, we will use Claude Code as the coding tool in the VS Code IDE.

To set this up, we need to add Creem’s SKILL.md file to our project, which is straightforward:

  1. Visit https://creem.io/SKILL.md
  2. Copy the content and create a file at .claude/skills/creem/SKILL.md in your project

That’s it — the skill has been added to the project.

Now, we will work through the following workflows using the Creem CLI and an AI coding tool:

  • Test the checkout flow and verify it with the Creem CLI
  • Handle state changes caused by webhook failures
  • Manage products and subscriptions
  • Create and manage discount codes

You can checkout the Github repo here: https://github.com/DeepKumbhare/creem-cli-tutorial

Workflows

Workflow 1: Testing the checkout flow and verifying it using the CREEM CLI.

We will ask Claude Code to generate the checkout the link for one of our product in store and it will use the CLI to generate the checkout link and will provide it to us. We will make a test payment and ask again Claude Code to verify the transaction.

I : we have a product image generator. Please create a checkout link for it and give it to me.

Claude code: Here’s your checkout link for the Image Generator product ($12.00/month): https://creem.io/test/checkout/prod_5PrU9h573ROasTsHXQkGTN/ch_2kR5r6O82HGmL82m47qZSI This is a test mode checkout. To test payments, use Stripe test card 4242 4242 4242 4242 with any future expiry and any CVC.

I have then made the payment with test card and asked Claude Code to confirm the payment.

I : we just have completed the payment can you please verify the checkout is successful. and it says

Claude Code:

Payment is confirmed! The most recent transaction shows:                                                               

  ┌─────────────────┬─────────────────────────────┐                                                                      
  │      Field      │            Value            │                                                                      
  ├─────────────────┼─────────────────────────────┤                                                                      
  │ Status          │ paid                        │                                                                      
  ├─────────────────┼─────────────────────────────┤                                                                      
  │ Amount          │ $12.00 USD                  │                                                                      
  ├─────────────────┼─────────────────────────────┤                                                                      
  │ Transaction ID  │ tran_4DCPbFnJcvTe7tIG7n9lqa │                                                                      
  ├─────────────────┼─────────────────────────────┤                                                                      
  │ Subscription ID │ sub_7CEoO2Ric0UpRvFCsOsOsg  │
  ├─────────────────┼─────────────────────────────┤                                                                      
  │ Customer ID     │ cust_7hy4RWKFhdErs8HnyjY4eq │                                                                    
  ├─────────────────┼─────────────────────────────┤                                                                      
  │ Type            │ Subscription invoice        │
  └─────────────────┴─────────────────────────────┘                                                                      

  The checkout was successful — a new subscription has been created for the Image Generator product.   

After this I have also confirmed this on my project’s subscription page:

status of subscription

it is showing in the active state so it means payment is successful and the subscription is active.

let’s move to next workflow.

Workflow 2: Verify State mismatch between Creem and Fix it.

Sometimes when user make some changes like pause the subscription from the Creem it doesn’t propagate properly to our application and this causes the mismatch of states between
Creem and our application. There may be multiple reason for this like when creem fired webhook, our endpoint was down or there may be some internal server error on our application side. So, in this workflow, we will find this mismatch and will try to fix this.

To perform this, we will tell Claude Code to pause the last subscription and then check with our app if it has also updated it’s state or not.

I : Pause my last subscription and check if our application has also updated the state of that transaction.

Claude Code:
I have updated the status of last subscription to paused.

The DB still shows status: active. The webhook from Creem hasn't updated it yet — this is likely because the app is

running on localhost, which Creem's webhook delivery can't reach from the internet.

Summary:

- Creem: sub_7CEoO2Ric0UpRvFCsOsOsg is now paused ✓
- DB (id: 13): still active — webhook not received

For webhooks to work locally, you need a tunnel (e.g. ngrok http 3000) forwarding requests to your local server, with

that public URL registered as the webhook endpoint in the Creem dashboard. Once that's in place, the

subscription.paused event will fire and your handler at /api/webhook/creem will update the DB status to paused.

At this point, it got confused because it assumed we were running the application on localhost and therefore would not be able to receive webhooks locally. Based on that assumption, it suggested using ngrok and trying again.

However, that was not actually the issue in our case, because we had already created and started an ngrok tunnel. So the next step is to clarify that ngrok is already running and ask it to check again and help identify the real issue.

I: ❯ ngrok is already working can you ploease check and help me to find the issue                                           

Claude Code: 

Found it. The subscription.paused handler at line 249–251 logs but does not update the DB:

case "subscription.paused":

console.log("Subscription paused:", [event.object.id](http://event.object.id/));

break;  // ← missing DB update

and it finally found out the issue 

The subscription.paused case was missing the DB update — it only logged and did nothing. Now it updates the status to
paused.

Since the webhook already fired and was missed, you can resend it from the Creem dashboard (Dashboard > Developers >

Webhooks > find the subscription.paused event > Resend), and the DB will update correctly this time.

At this point, it identified the actual issue. In our webhook handler for the subscription.paused event, we were only logging the event to the console and not creating an entry in the database.

This has now been fixed, and the webhook handler correctly stores the event in the database.

However, the current event still reflects a stale subscription status in our database. To correct this, we will re-fire the webhook event from the Creem dashboard so that it is processed again, this time with the fixed handler, and the database entry is created properly.

This is how the Creem CLI combined with AI, helps us identify state mismatches and resolve them effectively.

Workflow 3: manage products/subscriptions from the terminal

In this workflow, we will see how easily we can now manage products and subscription with help of Creem CLI and power of AI.

In this workflow, I will ask Claude Code to Create a new product and list all the products. and for subscription we will ask it to pause the subscription with the subscription id.

First, lets create a new product ‘AI Writer’. This is a subscription based saas product and price is 49$/month.

I: lets create a new product ai writer & it will be subscription product priced at 49$/month

Claude Code:

Product created successfully:                                                                                                         
  ┌──────────────┬─────────────────────────────┐                                
  │    Field     │            Value            │                                
  ├──────────────┼─────────────────────────────┤                                
  │ ID           │ prod_1FjKcB7j9b1WqrZFm1W1c7 │                                
  ├──────────────┼─────────────────────────────┤                                
  │ Name         │ AI Writer                   │                                
  ├──────────────┼─────────────────────────────┤                                
  │ Price        │ $49.00/month                │                                
  ├──────────────┼─────────────────────────────┤                                
  │ Billing      │ Recurring / month           │                                
  ├──────────────┼─────────────────────────────┤                                
  │ Tax Category │ SaaS                        │                                
  ├──────────────┼─────────────────────────────┤                                
  │ Mode         │ Test                        │                                
  └──────────────┴─────────────────────────────┘                                

  Product URL: https://creem.io/test/product/prod_1FjKcB7j9b1WqrZFm1W1c7             

It has created the product and also given me the product url.

Now We will List all the products.

I: List all the products of my store. 

Claude Code: You have 4 products in test mode:

  ┌─────────────────┬─────────┬───────────────────┬────────┐                    
  │      Name       │  Price  │      Billing      │ Status │                    
  ├─────────────────┼─────────┼───────────────────┼────────┤                    
  │ AI Writer       │ $49.00  │ Recurring / month │ active │                    
  ├─────────────────┼─────────┼───────────────────┼────────┤                    
  │ Image generator │ $12.00  │ Recurring / month │ active │                    
  ├─────────────────┼─────────┼───────────────────┼────────┤                    
  │ UI kit bundle   │ $199.00 │ One-time          │ active │                    
  ├─────────────────┼─────────┼───────────────────┼────────┤                    
  │ Vanilla         │ $100.00 │ One-time          │ active │
  └─────────────────┴─────────┴───────────────────┴────────┘               

Now, we will check with subscriptions. I want to pause the subscription with subscription id: sub_7CEoO2Ric0UpRvFCsOsOsg

I: Pause the subscription with ID: sub_7CEoO2Ric0UpRvFCsOsOsg
Claude Code: Done. Subscription sub_7CEoO2Ric0UpRvFCsOsOsg is now paused.                                                           
  ┌──────────┬───────────────────────────────────┐                              
  │  Field   │               Value               │                              
  ├──────────┼───────────────────────────────────┤                              
  │ Customer │ creem-test@gmail.com (Test creem) │                              
  ├──────────┼───────────────────────────────────┤                              
  │ Product  │ Image generator                   │                              
  ├──────────┼───────────────────────────────────┤                              
  │ Status   │ paused                            │                              
  └──────────┴───────────────────────────────────┘          

This is how easily we can manage products and subscription using a simple natural language.

Workflow 4: Manage discounts

In this workflow, We will create and manage discounts.

First, we will ask claude code to generate discount code for our product. we can also mention discount code.

I : create a discount code for product Image Generator keep coupon code TEST20 for 20% off. 

Here Claude Code will try to create the discount with API. rather than using the CLI. Because there is a catch in creem’s skill.md file they haven’t mentioned way to create discount with CLI. Instead it is written like:

### Flow 5: Discount codes

bash

CLI: Create a discount

(Use the SDK or API — the CLI does not have a discounts command yet)


However, claude code is so smart that it figures out the way by using the commands like `creem discounts create --help` and generate the discount code. 

Creem can easily fix this by adding discounts related stuff to [skills.md](http://skills.md) file. 

Here is what it can be added: 

tsx

Flow 5: Discount codes

Create a percentage discount (e.g., 20% off):

creem discounts create 
  --name "Launch Sale" 
  --code "LAUNCH20" 
  --type "percentage" 
  --percentage 20 
  --duration "forever" 
  --max-redemptions 20 
  --products prod_XXXXX

Create a fixed-amount discount (e.g., $10 off):

creem discounts create 
  --name "$10 Off" 
  --code "SAVE10" 
  --type "fixed" 
  --amount 1000 
  --currency "USD" 
  --duration "once" 
  --products prod_XXXXX

Discount creation options:

Option Type Required Description
--name string Yes Discount name (displayed in dashboard)
--code string No Discount code (auto-generated if not provided)
--type string Yes "percentage" or "fixed"
--percentage number Conditional Discount percentage 1-100 (required for percentage type)
--amount number Conditional Discount amount in cents (required for fixed type)
--currency string No Currency code for fixed discounts (default: USD)
--duration string Yes "once", "forever", or "repeating"
--duration-months number No Number of months for repeating duration
--max-redemptions number No Maximum uses before discount expires
--expires date No Expiry date (ISO format: 2025-12-31)
--products string Yes Comma-separated product IDs (e.g., prod_abc,prod_def)

Manage discounts:

# Get discount details by ID
creem discounts get disc_XXXXX --json

# Get discount details by code
creem discounts get --code LAUNCH20 --json

# Delete a discount
creem discounts delete disc_XXXXX

# View all discounts
# ℹ Discount list endpoint is not available via API
# View your discounts at: https://creem.io/dashboard/discounts

Now claude code will respond like: 

tsx
⏺ Discount code created:

┌──────────┬───────────────────────────────────┐

│ Field │ Value │

├──────────┼───────────────────────────────────┤

│ Code │ TEST20 │

├──────────┼───────────────────────────────────┤

│ Discount │ 20% off │

├──────────┼───────────────────────────────────┤

│ Duration │ Forever (applies to all payments) │

├──────────┼───────────────────────────────────┤

│ Product │ Image Generator │

├──────────┼───────────────────────────────────┤

│ Status │ active │

└──────────┴───────────────────────────────────┘

Customers can use TEST20 at checkout to get 20% off their Image Generator subscription.


Now, we can use this discount code on the product and confirm that this is working or not. 


![Discount code demo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o7vm70vz6tp9wn9l9f9g.png)

Wow 🤩, our discount code is working.

Now we will ask it to delete the discount code: TEST20. 

tsx
me: ❯ delete the discout code TEST20

cc: Discount code TEST20 has been deleted.




Discount code is now deleted and can’t be applied any more. 

We have seen total 4 workflows. I hoped you find them useful when working with Creem.

## Summary

Creem CLI completely changes the way developers handle payments by bringing everything directly into the terminal. Instead of juggling between dashboards, logs, and code, you can manage products, customers, subscriptions, transactions, and even checkouts from a single place.

When combined with AI coding tools like Claude Code, Cursor, or Windsurf, the experience becomes even more powerful. You can perform complex workflows using simple natural language. From generating checkout links and verifying payments to debugging webhook issues and fixing state mismatches.

In this guide, we explored how to:

- Set up and authenticate Creem CLI
- Manage products, customers, subscriptions, and transactions from the terminal
- Test and verify checkout flows using AI
- Detect and fix webhook/state sync issues
- Create and manage discounts

Creem CLI combined with AI provides faster smarter developer experience.

## 🎯 **Conclusion**

Creem CLI is more than just a command-line tool. it’s a productivity multiplier for developers working with payments.

By bringing everything into your terminal and pairing it with AI assistants, you unlock a new way of working:

- Less context switching
- Faster debugging
- Natural language-driven workflows

From creating products and managing subscriptions to troubleshooting webhook issues and generating discount codes, everything becomes faster and more intuitive.

👉 **GitHub Repository:**

https://github.com/DeepKumbhare/creem-cli-tutorial

TanStack Start Introduces Import Protection to Enforce Server and Client Boundaries

TanStack Start has introduced a import protection, which aims to prevent server and client code from being mixed in full-stack React applications. This Vite plugin automatically checks imports during development and build processes. It blocks harmful imports by file naming conventions or explicit markers, enhancing security and reducing bugs without requiring additional developer input.

By Daniel Curtis

ROBOTRAK'26 Ulusal Robot Yarışması ve Teknoloji Sergisi Kırklareli'nde yapılacak

Trakya Üniversiteler Birliği tarafından düzenlenecek ROBOTRAK’26 Ulusal Robot Yarışması ve Teknoloji Sergisi, 13 Mayıs Çarşamba günü Kırklareli Üniversitesi’nde gerçekleştirilecek. Etkinlikte, Türkiye genelinden üniversite ve lise öğrencileri ile akademisyenler projelerini sergileme ve çeşitli kategorilerde yarışma fırsatı bulacak.