Client SDKs

Official client libraries for Python, JavaScript/TypeScript, and Go.

The Vendel SDKs provide a simple interface for sending SMS, checking quotas, and verifying webhook signatures. They authenticate with integration API keys (vk_ prefix) — create one in the Dashboard under Settings → API Keys.

Installation

pip install vendel-sdk
npm install vendel-sdk
go get github.com/JimScope/vendel-sdk-go

The JS/TS SDK has zero runtime dependencies — uses built-in fetch (Node 18+). TypeScript types included.

Quick start

Send an SMS in three lines:

from vendel_sdk import VendelClient

client = VendelClient("https://app.vendel.cc", "vk_your_api_key")
response = client.send_sms(["+1234567890"], "Hello from Vendel!")
import { VendelClient } from "vendel-sdk";

const client = new VendelClient({
  baseUrl: "https://app.vendel.cc",
  apiKey: "vk_your_api_key",
});
const response = await client.sendSms(["+1234567890"], "Hello from Vendel!");
package main

import (
	"context"
	"fmt"
	vendel "github.com/JimScope/vendel-sdk-go"
)

func main() {
	client := vendel.NewClient("https://app.vendel.cc", "vk_your_api_key")
	resp, err := client.SendSMS(context.Background(), vendel.SendSMSRequest{
		Recipients: []string{"+1234567890"},
		Body:       "Hello from Vendel!",
	})
	if err != nil {
		panic(err)
	}
	fmt.Println("Batch ID:", resp.BatchID)
}

Send SMS

Send to one or more recipients in E.164 format. Optionally specify a device_id to route through a particular device.

response = client.send_sms(
    recipients=["+1234567890", "+0987654321"],
    body="Your order has shipped!",
    device_id="optional_device_id",
)
print(f"Batch: {response.batch_id}, Messages: {response.message_ids}")
const response = await client.sendSms(
  ["+1234567890", "+0987654321"],
  "Your order has shipped!",
  { deviceId: "optional_device_id" },
);
console.log(`Batch: ${response.batch_id}, Messages: ${response.message_ids}`);
resp, err := client.SendSMS(ctx, vendel.SendSMSRequest{
	Recipients: []string{"+1234567890", "+0987654321"},
	Body:       "Your order has shipped!",
	DeviceID:   "optional_device_id",
})
fmt.Printf("Batch: %s, Messages: %v\n", resp.BatchID, resp.MessageIDs)

Send to contact groups

Pass group_ids to include all members of one or more contact groups as recipients. Group members are merged with any explicit recipients you provide.

response = client.send_sms(
    recipients=["+1234567890"],
    body="Flash sale starts now!",
    group_ids=["group_abc", "group_xyz"],
)
print(f"Sent to {response.recipients_count} recipients")
const response = await client.sendSms(
  ["+1234567890"],
  "Flash sale starts now!",
  { groupIds: ["group_abc", "group_xyz"] },
);
console.log(`Sent to ${response.recipients_count} recipients`);
resp, err := client.SendSMS(ctx, vendel.SendSMSRequest{
	Recipients: []string{"+1234567890"},
	Body:       "Flash sale starts now!",
	GroupIDs:   []string{"group_abc", "group_xyz"},
})
fmt.Printf("Sent to %d recipients\n", resp.RecipientsCount)

Send SMS with template

Send personalized SMS using a saved template. Reserved variables ({{name}}, {{phone}}) are auto-filled from your contacts. Custom variables are passed as a key-value map.

response = client.send_sms_template(
    recipients=["+1234567890"],
    template_id="template_abc",
    variables={"code": "1234", "expiry": "10 minutes"},
    group_ids=["vip_customers"],
)
print(f"Batch: {response.batch_id}")
const response = await client.sendSmsTemplate(
  ["+1234567890"],
  "template_abc",
  {
    variables: { code: "1234", expiry: "10 minutes" },
    groupIds: ["vip_customers"],
  },
);
console.log(`Batch: ${response.batch_id}`);
resp, err := client.SendSMSTemplate(ctx, vendel.SendSMSTemplateRequest{
	Recipients: []string{"+1234567890"},
	TemplateID: "template_abc",
	Variables:  map[string]string{"code": "1234", "expiry": "10 minutes"},
	GroupIDs:   []string{"vip_customers"},
})
fmt.Printf("Batch: %s\n", resp.BatchID)

Get quota

Check your current plan limits and usage.

quota = client.get_quota()
print(f"Plan: {quota.plan}")
print(f"SMS: {quota.sms_sent_this_month}/{quota.max_sms_per_month}")
print(f"Devices: {quota.devices_registered}/{quota.max_devices}")
print(f"Resets: {quota.reset_date}")
const quota = await client.getQuota();
console.log(`Plan: ${quota.plan}`);
console.log(`SMS: ${quota.sms_sent_this_month}/${quota.max_sms_per_month}`);
console.log(`Devices: ${quota.devices_registered}/${quota.max_devices}`);
console.log(`Resets: ${quota.reset_date}`);
quota, err := client.GetQuota(ctx)
fmt.Printf("Plan: %s\n", quota.Plan)
fmt.Printf("SMS: %d/%d\n", quota.SMSSentThisMonth, quota.MaxSMSPerMonth)
fmt.Printf("Devices: %d/%d\n", quota.DevicesRegistered, quota.MaxDevices)
fmt.Printf("Resets: %s\n", quota.ResetDate)

Get message status

Check the delivery status of a single SMS message by its ID. See Message Status for the full API reference.

status = client.get_message_status("MESSAGE_ID")
print(f"Status: {status.status}")
print(f"Recipient: {status.recipient}")
if status.error_message:
    print(f"Error: {status.error_message}")
const status = await client.getMessageStatus("MESSAGE_ID");
console.log(`Status: ${status.status}`);
console.log(`Recipient: ${status.recipient}`);
if (status.error_message) {
  console.log(`Error: ${status.error_message}`);
}
status, err := client.GetMessageStatus(ctx, "MESSAGE_ID")
fmt.Printf("Status: %s\n", status.Status)
fmt.Printf("Recipient: %s\n", status.Recipient)
if status.ErrorMessage != "" {
	fmt.Printf("Error: %s\n", status.ErrorMessage)
}

Get batch status

When sending to multiple recipients, the response includes a batch_id. Use it to retrieve the status of all messages in the batch, including aggregate counts per status.

batch = client.get_batch_status("BATCH_ID")
print(f"Total: {batch.total}")
print(f"Status counts: {batch.status_counts}")
for msg in batch.messages:
    print(f"  {msg.recipient}: {msg.status}")
const batch = await client.getBatchStatus("BATCH_ID");
console.log(`Total: ${batch.total}`);
console.log(`Status counts:`, batch.status_counts);
for (const msg of batch.messages) {
  console.log(`  ${msg.recipient}: ${msg.status}`);
}
batch, err := client.GetBatchStatus(ctx, "BATCH_ID")
fmt.Printf("Total: %d\n", batch.Total)
fmt.Printf("Status counts: %v\n", batch.StatusCounts)
for _, msg := range batch.Messages {
	fmt.Printf("  %s: %s\n", msg.Recipient, msg.Status)
}

List contacts

Retrieve your contacts with optional search and group filtering. Results are paginated (default 50 per page, max 200).

contacts = client.list_contacts(search="Alice", per_page=20)
print(f"Found {contacts.total_items} contacts")
for c in contacts.items:
    print(f"  {c.name}: {c.phone_number}")
const contacts = await client.listContacts({ search: "Alice", perPage: 20 });
console.log(`Found ${contacts.total_items} contacts`);
for (const c of contacts.items) {
  console.log(`  ${c.name}: ${c.phone_number}`);
}
contacts, err := client.ListContacts(ctx, vendel.ListContactsParams{
	Search:  "Alice",
	PerPage: 20,
})
fmt.Printf("Found %d contacts\n", contacts.TotalItems)
for _, c := range contacts.Items {
	fmt.Printf("  %s: %s\n", c.Name, c.PhoneNumber)
}

List contact groups

Retrieve your contact groups. Use group IDs when sending to entire groups via group_ids.

groups = client.list_contact_groups(per_page=50)
print(f"Found {groups.total_items} groups")
for g in groups.items:
    print(f"  {g.id}: {g.name}")
const groups = await client.listContactGroups({ perPage: 50 });
console.log(`Found ${groups.total_items} groups`);
for (const g of groups.items) {
  console.log(`  ${g.id}: ${g.name}`);
}
groups, err := client.ListContactGroups(ctx, 1, 50)
fmt.Printf("Found %d groups\n", groups.TotalItems)
for _, g := range groups.Items {
	fmt.Printf("  %s: %s\n", g.ID, g.Name)
}

Webhook verification

When you receive a webhook, verify the X-Webhook-Signature header to ensure it came from Vendel. The signature is an HMAC-SHA256 hex digest of the request body.

Important: Always verify webhook signatures in production. Use the raw request body string for verification — do not re-serialize parsed JSON, as key ordering may differ.
from vendel_sdk import verify_webhook_signature

@app.route("/webhook", methods=["POST"])
def webhook():
    signature = request.headers.get("X-Webhook-Signature", "")
    is_valid = verify_webhook_signature(
        payload=request.get_data(as_text=True),
        signature=signature,
        secret="your_webhook_secret",
    )
    if not is_valid:
        return "Invalid signature", 401

    event = request.json
    print(f"Event: {event['event']}")
    return "OK", 200
import { verifyWebhookSignature } from "vendel-sdk";
import express from "express";

const app = express();
app.use(express.raw({ type: "application/json" }));

app.post("/webhook", (req, res) => {
  const signature = req.headers["x-webhook-signature"] as string;
  const isValid = verifyWebhookSignature(
    req.body.toString(),
    signature,
    "your_webhook_secret",
  );
  if (!isValid) return res.status(401).send("Invalid signature");

  const event = JSON.parse(req.body.toString());
  console.log("Event:", event.event);
  res.sendStatus(200);
});
import (
	"io"
	"net/http"
	vendel "github.com/JimScope/vendel-sdk-go"
)

func webhookHandler(w http.ResponseWriter, r *http.Request) {
	body, _ := io.ReadAll(r.Body)
	signature := r.Header.Get("X-Webhook-Signature")

	if !vendel.VerifyWebhookSignature(string(body), signature, "your_webhook_secret") {
		http.Error(w, "Invalid signature", http.StatusUnauthorized)
		return
	}

	// Process the event...
	w.WriteHeader(http.StatusOK)
}

Error handling

All SDKs throw typed errors. Quota errors (HTTP 429) include limit, used, and available fields so you can show meaningful messages to users.

from vendel_sdk import VendelClient, VendelAPIError, VendelQuotaError

try:
    client.send_sms(["+1234567890"], "Hello")
except VendelQuotaError as e:
    print(f"Quota exceeded: {e.used}/{e.limit} used, {e.available} remaining")
except VendelAPIError as e:
    print(f"API error [{e.status_code}]: {e.message}")
import { VendelClient, VendelQuotaError, VendelAPIError } from "vendel-sdk";

try {
  await client.sendSms(["+1234567890"], "Hello");
} catch (e) {
  if (e instanceof VendelQuotaError) {
    console.log(`Quota exceeded: ${e.used}/${e.limit} used, ${e.available} remaining`);
  } else if (e instanceof VendelAPIError) {
    console.log(`API error [${e.statusCode}]: ${e.message}`);
  }
}
resp, err := client.SendSMS(ctx, req)
if err != nil {
	if vendel.IsQuotaError(err) {
		qe := err.(*vendel.QuotaError)
		fmt.Printf("Quota exceeded: %d/%d used, %d remaining\n", qe.Used, qe.Limit, qe.Available)
	} else if vendel.IsAPIError(err) {
		ae := err.(*vendel.VendelError)
		fmt.Printf("API error [%d]: %s\n", ae.StatusCode, ae.Message)
	} else {
		fmt.Printf("Network error: %v\n", err)
	}
}