Mulakan
Contoh kod API SMS: curl, Node.js, Python, PHP, Go
Setiap contoh adalah program lengkap yang hanya menggunakan pustaka standard bahasanya (Node.js 18+ untuk fetch, Python 3, PHP dengan sambungan curl, Go 1.20+). Tetapkan BULKSMSAPI_KEY dalam persekitaran terlebih dahulu.
Hantar satu mesej
curl https://bulksmsapi.cc/v1/messages \
-H "Authorization: Bearer $BULKSMSAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+14155550123",
"text": "Your Northwind code is 482913. It expires in 10 minutes.",
"reference": "login-7781"
}'// Node.js 18 or newer (built-in fetch)
const res = await fetch("https://bulksmsapi.cc/v1/messages", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BULKSMSAPI_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
to: "+14155550123",
text: "Your Northwind code is 482913. It expires in 10 minutes.",
reference: "login-7781",
}),
});
const message = await res.json();
console.log(res.status, message.id, message.status, message.price);# Python 3, standard library only
import json, os, urllib.request
req = urllib.request.Request(
"https://bulksmsapi.cc/v1/messages",
data=json.dumps({
"to": "+14155550123",
"text": "Your Northwind code is 482913. It expires in 10 minutes.",
"reference": "login-7781",
}).encode(),
headers={
"Authorization": "Bearer " + os.environ["BULKSMSAPI_KEY"],
"Content-Type": "application/json",
"User-Agent": "northwind-app/1.0",
},
method="POST",
)
with urllib.request.urlopen(req) as res:
message = json.load(res)
print(res.status, message["id"], message["status"], message["price"])<?php
// PHP 7.4 or newer with the curl extension
$ch = curl_init("https://bulksmsapi.cc/v1/messages");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("BULKSMSAPI_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"to" => "+14155550123",
"text" => "Your Northwind code is 482913. It expires in 10 minutes.",
"reference" => "login-7781",
]),
]);
$message = json_decode(curl_exec($ch), true);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE), " ", $message["id"], " ", $message["status"], "\n";// Go 1.20 or newer, standard library only
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
body, _ := json.Marshal(map[string]any{
"to": "+14155550123",
"text": "Your Northwind code is 482913. It expires in 10 minutes.",
"reference": "login-7781",
})
req, _ := http.NewRequest("POST", "https://bulksmsapi.cc/v1/messages", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("BULKSMSAPI_KEY"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var message map[string]any
json.NewDecoder(res.Body).Decode(&message)
fmt.Println(res.StatusCode, message["id"], message["status"], message["price"])
}Hantar batch
curl https://bulksmsapi.cc/v1/batches \
-H "Authorization: Bearer $BULKSMSAPI_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: spring-sale-0412" \
-d '{
"name": "Spring sale",
"text": "Spring sale at Northwind: 20% off until Sunday. Opt out: northwind.example/sms",
"to": [
"+14155550123",
"+447400123456",
"+4915123456789"
]
}'const res = await fetch("https://bulksmsapi.cc/v1/batches", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BULKSMSAPI_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "spring-sale-0412",
},
body: JSON.stringify({
name: "Spring sale",
text: "Spring sale at Northwind: 20% off until Sunday. Opt out: northwind.example/sms",
to: ["+14155550123", "+447400123456", "+4915123456789"],
}),
});
const batch = await res.json();
console.log(res.status, batch.id, batch.recipients, batch.cost);import json, os, urllib.request
req = urllib.request.Request(
"https://bulksmsapi.cc/v1/batches",
data=json.dumps({
"name": "Spring sale",
"text": "Spring sale at Northwind: 20% off until Sunday. Opt out: northwind.example/sms",
"to": ["+14155550123", "+447400123456", "+4915123456789"],
}).encode(),
headers={
"Authorization": "Bearer " + os.environ["BULKSMSAPI_KEY"],
"Content-Type": "application/json",
"Idempotency-Key": "spring-sale-0412",
"User-Agent": "northwind-app/1.0",
},
method="POST",
)
with urllib.request.urlopen(req) as res:
batch = json.load(res)
print(res.status, batch["id"], batch["recipients"], batch["cost"])<?php
$ch = curl_init("https://bulksmsapi.cc/v1/batches");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("BULKSMSAPI_KEY"),
"Content-Type: application/json",
"Idempotency-Key: spring-sale-0412",
],
CURLOPT_POSTFIELDS => json_encode([
"name" => "Spring sale",
"text" => "Spring sale at Northwind: 20% off until Sunday. Opt out: northwind.example/sms",
"to" => ["+14155550123", "+447400123456", "+4915123456789"],
]),
]);
$batch = json_decode(curl_exec($ch), true);
echo $batch["id"], " ", $batch["recipients"], " ", $batch["cost"], "\n";package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
body, _ := json.Marshal(map[string]any{
"name": "Spring sale",
"text": "Spring sale at Northwind: 20% off until Sunday. Opt out: northwind.example/sms",
"to": []string{"+14155550123", "+447400123456", "+4915123456789"},
})
req, _ := http.NewRequest("POST", "https://bulksmsapi.cc/v1/batches", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("BULKSMSAPI_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", "spring-sale-0412")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var batch map[string]any
json.NewDecoder(res.Body).Decode(&batch)
fmt.Println(res.StatusCode, batch["id"], batch["recipients"], batch["cost"])
}Baca status mesej
curl https://bulksmsapi.cc/v1/messages/msg_8F3K2Q9TZ1MX \
-H "Authorization: Bearer $BULKSMSAPI_KEY"const res = await fetch("https://bulksmsapi.cc/v1/messages/msg_8F3K2Q9TZ1MX", {
headers: { Authorization: `Bearer ${process.env.BULKSMSAPI_KEY}` },
});
const message = await res.json();
console.log(message.status, message.error);import json, os, urllib.request
req = urllib.request.Request(
"https://bulksmsapi.cc/v1/messages/msg_8F3K2Q9TZ1MX",
headers={"Authorization": "Bearer " + os.environ["BULKSMSAPI_KEY"], "User-Agent": "northwind-app/1.0"},
)
with urllib.request.urlopen(req) as res:
message = json.load(res)
print(message["status"], message["error"])<?php
$ch = curl_init("https://bulksmsapi.cc/v1/messages/msg_8F3K2Q9TZ1MX");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("BULKSMSAPI_KEY")],
]);
$message = json_decode(curl_exec($ch), true);
echo $message["status"], "\n";package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest("GET", "https://bulksmsapi.cc/v1/messages/msg_8F3K2Q9TZ1MX", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("BULKSMSAPI_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var message map[string]any
json.NewDecoder(res.Body).Decode(&message)
fmt.Println(message["status"], message["error"])
}Semak baki
curl https://bulksmsapi.cc/v1/balance \
-H "Authorization: Bearer $BULKSMSAPI_KEY"const res = await fetch("https://bulksmsapi.cc/v1/balance", {
headers: { Authorization: `Bearer ${process.env.BULKSMSAPI_KEY}` },
});
const { balance, currency } = await res.json();
console.log(balance, currency);import json, os, urllib.request
req = urllib.request.Request(
"https://bulksmsapi.cc/v1/balance",
headers={"Authorization": "Bearer " + os.environ["BULKSMSAPI_KEY"], "User-Agent": "northwind-app/1.0"},
)
with urllib.request.urlopen(req) as res:
print(json.load(res)["balance"])<?php
$ch = curl_init("https://bulksmsapi.cc/v1/balance");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("BULKSMSAPI_KEY")],
]);
echo json_decode(curl_exec($ch), true)["balance"], "\n";package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest("GET", "https://bulksmsapi.cc/v1/balance", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("BULKSMSAPI_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var b struct{ Balance, Currency string }
json.NewDecoder(res.Body).Decode(&b)
fmt.Println(b.Balance, b.Currency)
}Baca harga
# Public: no key needed
curl https://bulksmsapi.cc/v1/pricing/DEconst res = await fetch("https://bulksmsapi.cc/v1/pricing");
const { data } = await res.json();
const de = data.find((row) => row.country === "DE");
console.log(data.length, de.price);import json, urllib.request
req = urllib.request.Request("https://bulksmsapi.cc/v1/pricing", headers={"User-Agent": "northwind-app/1.0"})
with urllib.request.urlopen(req) as res:
rows = json.load(res)["data"]
print(len(rows), next(r["price"] for r in rows if r["country"] == "DE"))<?php
$rows = json_decode(file_get_contents("https://bulksmsapi.cc/v1/pricing"), true)["data"];
foreach ($rows as $row) {
if ($row["country"] === "DE") echo $row["price"], "\n";
}package main
import (
"encoding/json"
"fmt"
"net/http"
)
func main() {
res, err := http.Get("https://bulksmsapi.cc/v1/pricing/DE")
if err != nil {
panic(err)
}
defer res.Body.Close()
var p struct{ Country, Price string }
json.NewDecoder(res.Body).Decode(&p)
fmt.Println(p.Country, p.Price)
}Sahkan webhook
Contoh Node.js menggunakan Express dan contoh Python menggunakan Flask, rangka kerja kecil biasa untuk titik akhir HTTP; semakan tandatangan itu sendiri hanya menggunakan pustaka standard.
// Express: keep the raw body, check the signature, then answer 2xx quickly
import crypto from "node:crypto";
import express from "express";
const app = express();
const secret = process.env.BULKSMSAPI_WEBHOOK_SECRET; // whsec_...
app.post("/hooks/sms", express.raw({ type: "application/json" }), (req, res) => {
const id = req.header("webhook-id");
const ts = req.header("webhook-timestamp");
const key = Buffer.from(secret.slice("whsec_".length), "base64url");
const expected = crypto.createHmac("sha256", key).update(`${id}.${ts}.${req.body}`).digest("base64");
const sent = (req.header("webhook-signature") || "").split(" ").map((s) => s.replace(/^v1,/, ""));
const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300;
if (!fresh || !sent.some((s) => s.length === expected.length && crypto.timingSafeEqual(Buffer.from(s), Buffer.from(expected)))) {
return res.status(401).end();
}
const event = JSON.parse(req.body);
console.log(event.type, event.data.id, event.data.status);
res.status(204).end();
});
app.listen(3000);# Flask: check the signature on the raw body before parsing it
import base64, hashlib, hmac, json, os, time
from flask import Flask, abort, request
app = Flask(__name__)
SECRET = os.environ["BULKSMSAPI_WEBHOOK_SECRET"] # whsec_...
@app.post("/hooks/sms")
def sms_hook():
msg_id = request.headers.get("webhook-id", "")
ts = request.headers.get("webhook-timestamp", "0")
raw = request.get_data()
key = base64.urlsafe_b64decode(SECRET[len("whsec_"):] + "==")
signed = msg_id.encode() + b"." + ts.encode() + b"." + raw
expected = base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest()).decode()
sent = [s.split(",", 1)[-1] for s in request.headers.get("webhook-signature", "").split()]
if abs(time.time() - int(ts)) > 300 or not any(hmac.compare_digest(s, expected) for s in sent):
abort(401)
event = json.loads(raw)
print(event["type"], event["data"]["id"], event["data"]["status"])
return "", 204<?php
// Plain PHP endpoint, for example /hooks/sms.php
$secret = getenv("BULKSMSAPI_WEBHOOK_SECRET"); // whsec_...
$raw = file_get_contents("php://input");
$id = $_SERVER["HTTP_WEBHOOK_ID"] ?? "";
$ts = $_SERVER["HTTP_WEBHOOK_TIMESTAMP"] ?? "0";
$key = base64_decode(strtr(substr($secret, 6), "-_", "+/"));
$expected = base64_encode(hash_hmac("sha256", "$id.$ts.$raw", $key, true));
$ok = false;
foreach (explode(" ", $_SERVER["HTTP_WEBHOOK_SIGNATURE"] ?? "") as $part) {
$sig = substr($part, strpos($part, ",") + 1);
if (hash_equals($expected, $sig)) $ok = true;
}
if (!$ok || abs(time() - (int)$ts) > 300) {
http_response_code(401);
exit;
}
$event = json_decode($raw, true);
error_log($event["type"] . " " . $event["data"]["id"]);
http_response_code(204);package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"io"
"log"
"math"
"net/http"
"os"
"strconv"
"strings"
"time"
)
func smsHook(w http.ResponseWriter, r *http.Request) {
secret := os.Getenv("BULKSMSAPI_WEBHOOK_SECRET") // whsec_...
key, _ := base64.RawURLEncoding.DecodeString(strings.TrimPrefix(secret, "whsec_"))
raw, _ := io.ReadAll(r.Body)
id, ts := r.Header.Get("webhook-id"), r.Header.Get("webhook-timestamp")
mac := hmac.New(sha256.New, key)
mac.Write([]byte(id + "." + ts + "." + string(raw)))
expected := base64.StdEncoding.EncodeToString(mac.Sum(nil))
sec, _ := strconv.ParseInt(ts, 10, 64)
ok := false
for _, part := range strings.Fields(r.Header.Get("webhook-signature")) {
sig := part[strings.Index(part, ",")+1:]
if hmac.Equal([]byte(sig), []byte(expected)) {
ok = true
}
}
if !ok || math.Abs(float64(time.Now().Unix()-sec)) > 300 {
w.WriteHeader(http.StatusUnauthorized)
return
}
var event struct {
Type string
Data map[string]any
}
json.Unmarshal(raw, &event)
log.Println(event.Type, event.Data["id"], event.Data["status"])
w.WriteHeader(http.StatusNoContent)
}
func main() {
http.HandleFunc("/hooks/sms", smsHook)
log.Fatal(http.ListenAndServe(":3000", nil))
}