Developers

HAMANI Marketing API

A versioned REST API (/api/v1) plus signed webhooks. Machine-readable spec: /api/v1/openapi.json. API access is included with your account — there is no separate API fee; message sends are billed at the same per-message rates as the app, and only when a message is accepted for sending by our delivery provider.

Authentication

Create keys in the app under API keys. Each key is scoped, shown once, and stored by us only as a hash. Send it on every request:

curl https://app.hamanimarketing.com.au/api/v1/wallet \
  -H "Authorization: Bearer hm_live_…"

Scopes: campaigns:read · campaigns:write · contacts:read · contacts:write · wallet:read · sends:read · webhooks:manage. Keep keys secret — you are responsible for their storage; revoke immediately if one leaks.

Endpoints

API endpoints
MethodPathScopeWhat it does
GET/api/v1/campaignscampaigns:readList campaigns. Every campaign carries locale + direction; per-language step content carries its own direction.
POST/api/v1/campaignscampaigns:writeCreate a DRAFT campaign. Activation is a separate call that runs the full Spam Act 2003 compliance gate. An unknown locale falls back to the default and sets locale_fallback:true — it never errors. Supports Idempotency-Key.
GET/api/v1/campaigns/{id}campaigns:readFetch one campaign.
PATCH/api/v1/campaigns/{id}campaigns:writePartial update. A patch that would leave an active campaign non-compliant is refused with the reasons.
DELETE/api/v1/campaigns/{id}campaigns:writeDelete a campaign.
POST/api/v1/campaigns/{id}/activatecampaigns:writeActivate — refused (422 with the reasons) unless sender identity and one-click unsubscribe are in place. Sends then dispatch on the schedule, inside the recipient's local send window, and debit only on an adapter-confirmed send. Supports Idempotency-Key.
GET/api/v1/contactscontacts:readList contacts (preferred_language + direction included).
POST/api/v1/contactscontacts:writeCreate/upsert a contact by email. consent_basis (how the person gave permission) is REQUIRED — the Spam Act consent gate. Supports Idempotency-Key.
GET/api/v1/contacts/{id}contacts:readFetch one contact.
PATCH/api/v1/contacts/{id}contacts:writeUpdate a contact. The API can set an opt-out but can never UNDO one — re-consent must come from the person.
DELETE/api/v1/contacts/{id}contacts:writeDelete a contact.
GET/api/v1/walletwallet:readAvailable prepaid balance + validity. Read-only: the API has no spend surface; sends debit only on an adapter-confirmed send (provider acceptance).
GET/api/v1/sendssends:readSend statuses from the audit records: delivered (the only billed status), queued, failed (auto-retried, never charged), simulated, suppressed. Filter with ?campaign_id=.
POST/api/v1/one-clickcampaigns:writeOne-click campaign: without approve → proposal + compliance warnings + Waste Filter savings; with approve → full gate, then create + activate. Standing waste flags need override_waste:true (logged). Supports Idempotency-Key.
GET/api/v1/webhookswebhooks:manageList webhook endpoints (signing secrets masked).
POST/api/v1/webhookswebhooks:manageRegister an endpoint (https). The signing secret is returned ONCE — verify hamani-signature (HMAC-SHA256 of "timestamp.body") on every delivery. Supports Idempotency-Key.
DELETE/api/v1/webhooks/{id}webhooks:manageRemove a webhook endpoint.

Example — create and activate a campaign

# 1 · create a draft (Idempotency-Key makes retries safe)
curl -X POST https://app.hamanimarketing.com.au/api/v1/campaigns \
  -H "Authorization: Bearer hm_live_…" \
  -H "Idempotency-Key: create-winter-2026" \
  -H "Content-Type: application/json" \
  -d '{"name":"Winter specials","locale":"ne",
       "sender_name":"Asha's Café",
       "sender_identity":"Asha's Café, 12 Main St, Parramatta NSW",
       "steps":[{"id":"s1","channel":"email",
                 "subject":"Hi {{first_name}}",
                 "body":"Our winter menu is here.\n\n{{unsubscribe}}",
                 "delay_hours":0}]}'

# 2 · activate (runs the Spam Act compliance gate — 422 with reasons if not ready)
curl -X POST https://app.hamanimarketing.com.au/api/v1/campaigns/CAMPAIGN_ID/activate \
  -H "Authorization: Bearer hm_live_…"

Webhooks

Register endpoints via POST /api/v1/webhooks. Events: send.delivered · send.failed · campaign.completed · wallet.low · wallet.expiring · contact.unsubscribed. Every delivery is signed — verify before trusting:

// header: hamani-signature: t=1780000000,v1=hex
const [t, v1] = parseHeader(req.headers["hamani-signature"]);
const expected = hmacSha256Hex(yourEndpointSecret, t + "." + rawBody);
if (v1 !== expected) reject();   // and check t is recent

Failed deliveries retry with exponential backoff (up to 8 attempts). An endpoint that fails 10 times in a row is switched off so retries can’t pile up — fix it and register it again. Return any 2xx to acknowledge.

Rate limits & idempotency

300 requests per 5 minutes per key (429 with Retry-After beyond that). POST endpoints accept an Idempotency-Key header: a retry with the same key returns the stored result and never acts twice.

The rules the API enforces for you