GlobalgleDevelopers
  • Platform
  • Docs
  • Pricing
Sign inGet started
GlobalgleDevelopers
PlatformDocsPricingSign in
© 2026 Globalgle Developers. All rights reserved.

Getting started

  • Introduction
  • Authentication
  • Errors
  • Discovery
  • Rate limits
  • Idempotency
  • Webhooks
Sign in to browse all endpoints

Looking for a specific endpoint?

Every /v1 route — with parameters and copy-ready examples — lives in the Endpoints explorer inside your dashboard. This page covers the concepts you need first.

Sign in to browse all endpoints

Introduction

The Globalgle API is a REST API over HTTPS. All requests go to the versioned base URL below; responses are JSON. One API covers the whole platform. Call GET /catalog to discover every service, its scopes and required fields at runtime.

Base URL

https://tudowebs.com/api/v1

You need a developer account and an active API plan. Create a key in your dashboard, then send it as a Bearer token.

Authentication

Authenticate every request with your secret API key in the Authorization header. Keys look like gg_live_…. From the API Keys page you can view one again, or rotate it for a fresh secret, by confirming your password. Keep them secret; never embed them in client-side code.

Authorization: Bearer gg_live_your_key_here

You can also pass the key in an x-api-key header instead of Authorization — use whichever your stack makes easier.

x-api-key: gg_live_your_key_here

A missing or invalid key returns 401. A valid key without an active API plan returns 403 plan_required.

Errors

Errors use standard HTTP status codes and a JSON body:

{ "error": "insufficient_balance",
  "message": "Your wallet balance is too low for this request." }
401

invalid_key

Missing, unknown, revoked, or expired key

403

plan_required

No active API plan

403

scope_forbidden

Key is missing the required scope

403

ip_not_allowed

Source IP not in the key’s allowlist

403

…_not_in_plan

Your plan doesn’t include this service (e.g. websites_not_in_plan)

403

site_limit_reached

Website allowance used up and your plan doesn’t sell extras

402

insufficient_balance

Wallet balance too low

404

not_found

No such order, id, or resource

409

out_of_stock

Item/number unavailable (returned before any charge)

422

validation_error

Invalid or missing parameters

429

rate_limited

Too many requests — see retry_after_seconds

502

provider_error

Upstream fulfilment failed (wallet auto-refunded)

503

service_unavailable

Service disabled or temporarily unavailable

503

api_disabled

The API is paused platform-wide

Discovery (catalog)

One call describes everything available — each service, its scopes and the required fields for its actions, plus the site types you can create. Read it once; new products and site types appear automatically, so you never have to re-integrate. Requires an active plan.

curl https://tudowebs.com/api/v1/catalog \
  -H "Authorization: Bearer gg_live_your_key_here"
GET /api/v1/catalog
Authorization: Bearer gg_live_…

200 OK
{
  "services": [
    { "slug": "<service>", "name": "<Service name>", "category": "<Category>",
      "base": "/v1/<service>", "scopes": ["<service>:read","<service>:write"],
      "actions": [ { "method": "POST", "path": "/v1/<service>/<action>", "required": ["<field>","<field>"] } ] },
    { "slug": "<site-service>", "name": "<Site service>", "category": "<Category>",
      "types": [ { "type": "<type>", "name": "<Type name>" }, { "type": "<type>", "name": "<Type name>" } ] }
  ]
}

Rate limits

Each API key has a request limit set by your plan. When you exceed it you receive 429 with a retry_after_seconds field and a Retry-After header — wait that long, then retry. Every response also returns your remaining budget in the X-RateLimit-Remaining header, so you can throttle before you hit the limit.

Idempotency

To make charge-creating retries safe, send a unique Idempotency-Key header on write endpoints (any POST that creates a charge). A repeated request with the same key returns the original result instead of creating (and charging) a second one.

Idempotency-Key: 6f9c2b7a-1f2e-4d3c-9a10-b8e7c6d5f4a3

Webhooks

Instead of polling, register a webhook endpoint in your dashboard to receive events as a push. Each endpoint has its own signing secret (whsec_…).

Events are delivered as a POST with these headers and body:

X-Webhook-Signature: sha256=<hmac>
X-Webhook-Timestamp: 1752570000
Content-Type: application/json

{
  "id": "evt_ab12cd34",
  "event": "sms.verification.completed",
  "created": 1752570000,
  "data": { "id": 10432, "code": "123456", "phone_number": "+234…", "service_name": "telegram" }
}

Verify the signature: compute the HMAC below as hex and compare it (constant-time) to the value after sha256=. Reject if the timestamp is more than a few minutes old (replay protection). Respond 2xx to acknowledge; a non-2xx is retried once.

signature = HMAC_SHA256(secret, timestamp + "." + rawRequestBody)

Sign the raw request bytes exactly as received — don't JSON.parse then re-serialize. Re-stringifying reorders keys and changes spacing, so the signature will never match.

Events

Pick specific events when you register an endpoint, or leave the selection empty to receive all of them. The data each event carries:

SMS Verification
  sms.verification.completed    { id, code, phone_number, service_name }

SMS Sender
  sms_sender.message.delivered  { id, to, status }
  sms_sender.message.failed     { id, to, status }

Domains
  domain.registered             { id, domain, expires_at, nameservers }
  domain.renewed                { id, expires_at }
  domain.banned                 { domain, reason, banned_by }
  domain.refunded               { id, domain, amount_ngn, refunded_by }
  domain.renewal_refunded       { id, domain, amount_ngn, renewals, reason, refunded_by }
  domain.updated                { id, domain, auto_renew }

Buy Webmail
  mailbox.created               { id, email }
  mailbox.deleted               { id }

Websites
  website.created               { type, site }
  website.updated               { site, change?, host?, old_host? }
  website.subdomain_changed     { site, old_url }
  website.admin_login_changed   { site }
  website.sites_moved           { from_domain, to_domain, count }
  website.deleted               { ref, type, name, host, site? }

  website.sites_moved fires ONCE when we move a shared subdomain to another
  domain. It covers every site of yours that was on it, so it carries a count,
  not a list: re-list your websites for the new urls. Sites we could not
  move are excluded and still answer on the old address.

Marketplace
  marketplace.order.created     { order }
  marketplace.order.fulfilled   { order }

Buy Account
  buy_account.order.completed   { order }

Account Verification
  account_verification.application.approved
                                { id, status, starts_at, expires_at, duration_days }
  account_verification.application.rejected
                                { id, reason, refunded }

Custom Caller  (every call.* payload carries call: { object, id, session_id })
  call.dialing / call.ringing / call.answered   { call, status }
  call.completed                { call, status, duration_seconds, recording_available }
  call.failed                   { call, status, reason }
  call.refunded                 { call, status }
  call.recording.ready          { call, duration_seconds }
  call.code_captured  (opt-in)  { call, captured_code, code_length }
  call.field_captured (opt-in)  { call, field, value, is_first_entry }
  call.dtmf_progress  (opt-in)  { call, digits }
  call.transcript     (opt-in)  { call, speaker, text }
  call.ai_response    (opt-in)  { call, speaker, text }

Custom Caller — legacy (frozen, still fired alongside the call.* events above)
  spoof.call.failed             { id }
  spoof.clone.created           { id, voice_id, name }
  spoof.call.completed          RETIRED — now an alias of call.completed.
                                Nothing to change: an endpoint still subscribed
                                to it receives call.completed instead.

Wallet Funding (buy crypto)
  buy_crypto.order.created      { id, status, charge_usd, usd_amount, coin_id, receive_address }
  buy_crypto.order.sent         { id, status, coin_name, coin_symbol, usd_amount, receive_address, tx_hash }
  buy_crypto.order.rejected     { id, status, coin_name, usd_amount, reason, refunded_usd }

Wallet
  wallet.credited               { amount_ngn, amount_usd, currency, reference }

Pricing
  pricing.changed               { reason, services, effective_at }

  Your prices moved: re-read the price endpoints for the services listed
  (an empty list means all of them). reason is "plan_updated" (we edited
  the plan you are on), "plan_changed" (you subscribed, switched plan, or
  your plan ended) or "price_updated" (a service price we set changed).
  Orders are always charged at the current price, so a stored copy of a
  price is stale from effective_at.

New integrations should use the call.* events above: they are pushed live from the call engine, so you get dialing/ringing/answered, the recording, and the opt-in transcript and keypad streams as they happen. spoof.call.failed and spoof.clone.created are frozen so existing integrations don't break, and still fire alongside the call.* events.

Retired: spoof.call.completed is no longer sent under that name — it is now an alias of call.completed. If your endpoint is subscribed to it you need to change nothing: you will receive call.completed, which carries recording_available as a boolean rather than a recording URL. Fetch the audio from the authenticated recording endpoint after call.recording.ready. The spellings custom_caller.call.completed and custom_caller.call.failed are also accepted when you subscribe, and are stored under their wire names (call.completed and spoof.call.failed).