Skip to content

Purchase Credits

Learn how to purchase credits using Stripe Checkout for seamless, secure payments.

Overview

YeboLink uses Stripe for secure credit card payments. The checkout process is simple:

  1. Create a checkout session via API
  2. Redirect user to Stripe Checkout
  3. User completes payment
  4. Credits are automatically added to your account
  5. Receive confirmation via webhook

Create Checkout Session

Create a Stripe Checkout session to purchase credits.

Endpoint

POST /api/v1/billing/checkout

Authentication

Requires JWT token (dashboard authentication).

Authorization: Bearer YOUR_JWT_TOKEN

Request

bash
curl -X POST https://api.yebolink.com/api/v1/billing/checkout \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "credits": 500
  }'
javascript
const createCheckoutSession = async (credits) => {
  const response = await fetch('https://api.yebolink.com/api/v1/billing/checkout', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${jwtToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ credits })
  });

  const data = await response.json();

  // Redirect to Stripe Checkout
  window.location.href = data.data.url;
};

// Usage
await createCheckoutSession(500);
python
import requests

def create_checkout_session(jwt_token: str, credits: int):
    response = requests.post(
        'https://api.yebolink.com/api/v1/billing/checkout',
        headers={
            'Authorization': f'Bearer {jwt_token}',
            'Content-Type': 'application/json'
        },
        json={'credits': credits}
    )

    data = response.json()

    # Redirect user to this URL
    checkout_url = data['data']['url']
    return checkout_url

# Usage
checkout_url = create_checkout_session(jwt_token, 500)
print(f"Redirect to: {checkout_url}")
php
<?php
function createCheckoutSession($jwtToken, $credits) {
    $curl = curl_init();

    curl_setopt_array($curl, [
        CURLOPT_URL => 'https://api.yebolink.com/api/v1/billing/checkout',
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST => true,
        CURLOPT_HTTPHEADER => [
            'Authorization: Bearer ' . $jwtToken,
            'Content-Type: application/json'
        ],
        CURLOPT_POSTFIELDS => json_encode([
            'credits' => $credits
        ])
    ]);

    $response = curl_exec($curl);
    $data = json_decode($response, true);
    curl_close($curl);

    // Redirect user to Stripe Checkout
    header('Location: ' . $data['data']['url']);
    exit;
}

// Usage
createCheckoutSession($jwtToken, 500);
?>
ruby
require 'net/http'
require 'json'
require 'uri'

def create_checkout_session(jwt_token, credits)
  uri = URI('https://api.yebolink.com/api/v1/billing/checkout')
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true

  request = Net::HTTP::Post.new(uri)
  request['Authorization'] = "Bearer #{jwt_token}"
  request['Content-Type'] = 'application/json'
  request.body = { credits: credits }.to_json

  response = http.request(request)
  data = JSON.parse(response.body)

  # Redirect user to this URL
  data['data']['url']
end

# Usage
checkout_url = create_checkout_session(jwt_token, 500)
puts "Redirect to: #{checkout_url}"
go
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
)

type CheckoutRequest struct {
    Credits int `json:"credits"`
}

type CheckoutResponse struct {
    Success bool `json:"success"`
    Data    struct {
        SessionID string `json:"session_id"`
        URL       string `json:"url"`
    } `json:"data"`
}

func createCheckoutSession(jwtToken string, credits int) (string, error) {
    reqBody := CheckoutRequest{Credits: credits}
    jsonData, _ := json.Marshal(reqBody)

    req, _ := http.NewRequest("POST",
        "https://api.yebolink.com/api/v1/billing/checkout",
        bytes.NewBuffer(jsonData))

    req.Header.Set("Authorization", "Bearer "+jwtToken)
    req.Header.Set("Content-Type", "application/json")

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)

    var result CheckoutResponse
    json.Unmarshal(body, &result)

    // Redirect user to this URL
    fmt.Println("Redirect to:", result.Data.URL)

    return result.Data.URL, nil
}

Parameters

ParameterTypeRequiredDescription
creditsintegerYesNumber of credits to purchase (100, 500, 1000, 5000, 10000)

Response

json
{
  "success": true,
  "data": {
    "session_id": "cs_test_a1b2c3d4e5f6g7h8i9j0",
    "url": "https://checkout.stripe.com/c/pay/cs_test_..."
  }
}

What Happens Next

  1. Redirect to Stripe: Send user to the url from the response
  2. User Pays: User completes payment on Stripe Checkout
  3. Webhook Triggered: Stripe notifies YeboLink of successful payment
  4. Credits Added: Credits are automatically added to workspace
  5. Redirect Back: User is redirected back to your success URL

Complete Integration Example

Frontend (React)

javascript
import { useState } from 'react';

function PurchaseCreditsButton({ credits, price }) {
  const [loading, setLoading] = useState(false);

  const handlePurchase = async () => {
    setLoading(true);

    try {
      // Get JWT token from your auth system
      const jwtToken = localStorage.getItem('jwt_token');

      // Create checkout session
      const response = await fetch('https://api.yebolink.com/api/v1/billing/checkout', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${jwtToken}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ credits })
      });

      const data = await response.json();

      if (data.success) {
        // Redirect to Stripe Checkout
        window.location.href = data.data.url;
      } else {
        alert('Error creating checkout session');
      }
    } catch (error) {
      console.error('Error:', error);
      alert('Failed to initiate checkout');
    } finally {
      setLoading(false);
    }
  };

  return (
    <button
      onClick={handlePurchase}
      disabled={loading}
      className="purchase-button"
    >
      {loading ? 'Processing...' : `Buy ${credits} Credits - $${price}`}
    </button>
  );
}

// Usage
function BillingPage() {
  return (
    <div>
      <h1>Purchase Credits</h1>
      <PurchaseCreditsButton credits={500} price={45} />
      <PurchaseCreditsButton credits={1000} price={80} />
      <PurchaseCreditsButton credits={5000} price={350} />
    </div>
  );
}

Backend Webhook Handler (Node.js/Express)

javascript
const express = require('express');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const app = express();

// Important: Use raw body for Stripe webhook signature verification
app.post(
  '/webhooks/stripe',
  express.raw({ type: 'application/json' }),
  async (req, res) => {
    const sig = req.headers['stripe-signature'];
    const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;

    let event;

    try {
      // Verify webhook signature
      event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
    } catch (err) {
      console.error('Webhook signature verification failed:', err.message);
      return res.status(400).send(`Webhook Error: ${err.message}`);
    }

    // Handle the event
    if (event.type === 'checkout.session.completed') {
      const session = event.data.object;

      console.log('Payment successful!');
      console.log('Workspace ID:', session.client_reference_id);
      console.log('Credits purchased:', session.metadata.credits);

      // Credits are automatically added by YeboLink's webhook handler
      // You can add additional logic here (send email, update UI, etc.)
    }

    res.json({ received: true });
  }
);

Stripe Checkout Success & Cancel URLs

When creating a checkout session, Stripe will redirect users to:

Success URL (after successful payment):

https://your-app.com/billing/success?session_id={CHECKOUT_SESSION_ID}

Cancel URL (if user cancels):

https://your-app.com/billing/cancel

Handling Success Page

javascript
// pages/billing/success.js
import { useEffect, useState } from 'react';
import { useSearchParams } from 'next/navigation';

export default function BillingSuccess() {
  const searchParams = useSearchParams();
  const sessionId = searchParams.get('session_id');
  const [status, setStatus] = useState('loading');

  useEffect(() => {
    if (sessionId) {
      // Verify the session and show success message
      fetch(`/api/checkout-session?session_id=${sessionId}`)
        .then(res => res.json())
        .then(data => {
          if (data.payment_status === 'paid') {
            setStatus('success');
          } else {
            setStatus('error');
          }
        });
    }
  }, [sessionId]);

  if (status === 'loading') {
    return <div>Processing your payment...</div>;
  }

  if (status === 'success') {
    return (
      <div>
        <h1>Payment Successful!</h1>
        <p>Your credits have been added to your account.</p>
        <a href="/dashboard">Go to Dashboard</a>
      </div>
    );
  }

  return <div>Payment verification failed. Please contact support.</div>;
}

Testing

Test Mode

Use Stripe's test mode for development:

javascript
// Test card numbers
const testCards = {
  success: '4242 4242 4242 4242',
  decline: '4000 0000 0000 0002',
  requiresAuth: '4000 0025 0000 3155'
};

Test Checkout Flow

  1. Create checkout session with test API keys
  2. Use test card 4242 4242 4242 4242
  3. Use any future expiry date
  4. Use any 3-digit CVC
  5. Use any billing ZIP code

Error Handling

Insufficient Credits Error

javascript
async function createCheckoutSession(credits) {
  try {
    const response = await fetch('https://api.yebolink.com/api/v1/billing/checkout', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${jwtToken}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ credits })
    });

    const data = await response.json();

    if (!response.ok) {
      if (response.status === 400) {
        throw new Error('Invalid credit amount. Choose: 100, 500, 1000, 5000, or 10000');
      }
      if (response.status === 401) {
        throw new Error('Please login to purchase credits');
      }
      throw new Error(data.error || 'Failed to create checkout session');
    }

    return data.data.url;
  } catch (error) {
    console.error('Checkout error:', error);
    throw error;
  }
}

Webhook Events

After successful payment, YeboLink sends a webhook to your configured URL with the billing.credits_purchased event:

json
{
  "event": "billing.credits_purchased",
  "timestamp": "2025-11-02T15:00:00Z",
  "data": {
    "workspace_id": "550e8400-e29b-41d4-a716-446655440000",
    "credits_purchased": 500,
    "amount_paid": 45.00,
    "currency": "USD",
    "transaction_id": "txn_1234567890",
    "new_balance": 750
  }
}

See Webhooks Documentation for more details.


Security Best Practices

1. Never Store Card Details

DANGER

Never store credit card information yourself. Always use Stripe Checkout for PCI compliance.

2. Verify Webhook Signatures

javascript
// Always verify Stripe webhook signatures
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

app.post('/webhooks/stripe', async (req, res) => {
  const sig = req.headers['stripe-signature'];

  try {
    const event = stripe.webhooks.constructEvent(
      req.body,
      sig,
      process.env.STRIPE_WEBHOOK_SECRET
    );

    // Process event
  } catch (err) {
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }
});

3. Use HTTPS Only

javascript
// Ensure HTTPS in production
if (process.env.NODE_ENV === 'production' && req.protocol !== 'https') {
  return res.redirect('https://' + req.get('host') + req.url);
}

Pricing Calculator

Calculate costs before purchasing:

javascript
function calculateCost(messageCount, channel) {
  const creditCosts = {
    sms: 1,
    whatsapp: 0.5,
    email: 0.1,
    voice: 2  // per minute
  };

  const packages = [
    { credits: 100, price: 10, perCredit: 0.10 },
    { credits: 500, price: 45, perCredit: 0.09 },
    { credits: 1000, price: 80, perCredit: 0.08 },
    { credits: 5000, price: 350, perCredit: 0.07 },
    { credits: 10000, price: 600, perCredit: 0.06 }
  ];

  const creditsNeeded = messageCount * creditCosts[channel];

  // Find best package
  const bestPackage = packages.find(pkg => pkg.credits >= creditsNeeded) ||
                       packages[packages.length - 1];

  const estimatedCost = creditsNeeded * bestPackage.perCredit;

  return {
    messagesCount: messageCount,
    channel,
    creditsNeeded,
    recommendedPackage: bestPackage.credits,
    estimatedCost: estimatedCost.toFixed(2),
    pricePerMessage: (estimatedCost / messageCount).toFixed(4)
  };
}

// Example
const cost = calculateCost(1000, 'sms');
console.log(cost);
// {
//   messagesCount: 1000,
//   channel: 'sms',
//   creditsNeeded: 1000,
//   recommendedPackage: 1000,
//   estimatedCost: '80.00',
//   pricePerMessage: '0.0800'
// }

FAQs

Do credits expire?

No! YeboLink credits never expire. Purchase credits and use them at your own pace.

Can I get a refund?

Credits purchased are non-refundable, but credits for failed messages are automatically refunded.

What payment methods are accepted?

We accept all major credit cards via Stripe (Visa, Mastercard, American Express, Discover).

Can I get an invoice?

Yes! Invoices are automatically sent to your email after each purchase and are available in your dashboard.

Do you offer bulk discounts?

Yes! Larger credit packages have lower per-credit costs. Contact enterprise@yebolink.app for custom pricing.


Next Steps

Need Help?

Built with VitePress