# Splashify Pro Partner API — Complete Documentation > This is the full-text version of the Splashify Pro partner documentation. > Generated for AI consumption. Source: https://partner-docs.splashifypro.com > Generated at: 2026-06-24T04:04:45.214Z > Total pages: 121 --- ======================================================================== ## Splashify Pro Email API URL: https://partner-docs.splashifypro.com/ ======================================================================== # Splashify Pro Email API Splashify Pro is an email API for developers. The partner API gives you the building blocks to ship transactional and marketing email at scale: verified sending identities, configuration sets, templates, suppression lists, real-time webhooks, and deliverability-grade reputation tracking. The API is shaped like AWS SES. If you've integrated against AWS SES before, you'll feel at home — sending an email, configuring an event destination, or polling send statistics each map onto a familiar endpoint with the same field names. ## Base URL All API requests are made to: ``` https://api.splashifypro.com/api/v1/partner/email ``` ## Authentication Every API request must carry your secret API key in the `Authorization` header: ```bash Authorization: Bearer pk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ``` Generate a key from your partner dashboard at [partner.splashifypro.com](https://partner.splashifypro.com) under **Settings → API Keys**. The key is shown once — store it in a secure secret manager. > **Security:** Treat your API key like a password. Never embed it in > client-side code, mobile apps, or public repositories. ## Quick example Send a transactional email with two lines of curl: ```bash curl https://api.splashifypro.com/api/v1/partner/email/send \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": "alerts@yourcompany.com", "to": ["customer@example.com"], "subject": "Your order has shipped", "html_body": "

Tracking: ABC123

", "text_body": "Tracking: ABC123" }' ``` Response: ```json { "success": true, "results": [ { "recipient": "customer@example.com", "message_id": "f9c3a2b1-...", "status": "queued" } ] } ``` ## API surface at a glance | Action | Endpoint | AWS SES equivalent | |---|---|---| | Send transactional | `POST /send` | `SendEmail` | | Send raw MIME | `POST /send-raw` | `SendRawEmail` | | Send templated | `POST /send-template` | `SendTemplatedEmail` | | Bulk templated | `POST /send-bulk` | `SendBulkTemplatedEmail` | | Verify domain / address | `POST /identities` | `CreateEmailIdentity` | | Configuration sets | `/configuration-sets` | `CreateConfigurationSet`... | | Event destinations | `/configuration-sets/:id/event-destinations` | `CreateConfigurationSetEventDestination` | | Templates | `/templates` | `CreateEmailTemplate`... | | Suppression list | `/suppression` | `PutSuppressedDestination`... | | Send quota | `GET /quotas` | `GetSendQuota` | | Send statistics | `GET /stats` | `GetSendStatistics` | | Reputation | `GET /reputation` | `GetAccountReputation` | | Production access | `POST /production-access` | Submit support case | ## Response format Success responses always include `success: true`: ```json { "success": true, "data": { ... } } ``` Error responses carry a stable `error` code + a human-readable `message`: ```json { "success": false, "error": "INVALID_REQUEST", "message": "from address is not on a verified identity" } ``` ## HTTP status codes | Code | Meaning | |---|---| | `200` | Success | | `201` | Resource created | | `400` | Bad request — fix your inputs | | `401` | Missing / invalid API key | | `402` | Insufficient wallet balance — top up | | `403` | Sending paused, sandbox cap reached, or feature locked | | `404` | Resource not found | | `409` | Conflict (duplicate name, etc.) | | `429` | Rate limit hit — back off | | `500` | Server error — retry with backoff | | `503` | Database / dependency unavailable | ## Get started - [**Getting Started →**](/getting-started) — first-send walkthrough - [**Concepts →**](/concepts) — sending identities, config sets, sandbox - [**API Reference →**](/api-reference) — every endpoint - [**Webhooks →**](/webhooks) — receive delivery events - [**Deliverability →**](/deliverability) — SPF/DKIM/DMARC and reputation - [**Pricing →**](/pricing) — flat ₹0.01 per email ======================================================================== ## Getting Started URL: https://partner-docs.splashifypro.com/getting-started ======================================================================== # Getting Started This guide walks you through the steps to send your first email through the Splashify Pro Email API. ## Prerequisites 1. A Splashify Pro partner account ([sign up free](https://partner.splashifypro.com/auth/signup)) 2. An API key (generated from your partner dashboard) 3. A domain you control (for production sends — sandbox sends work without a verified domain but only to addresses you've verified) ## Step 1 — Sign up and get an API key If you don't have an account: 1. Visit [partner.splashifypro.com/auth/signup](https://partner.splashifypro.com/auth/signup) 2. Enter your email, mobile number (with country code), and a password 3. Verify the OTP sent to **both** your email and WhatsApp 4. Log in To create an API key: 1. Go to **Settings → API Keys** 2. Click **Generate API Key** 3. Copy and securely store the key — it is only shown once 4. Use it as the Bearer token in the `Authorization` header on every API request ## Step 2 — Verify a sending identity Before you can send from an address, you must verify ownership of the domain or the email address itself. Domain verification is preferred because it covers any address at that domain. ```bash curl https://api.splashifypro.com/api/v1/partner/email/identities \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "identity_type": "DOMAIN", "identity_value": "yourcompany.com" }' ``` The response includes the three DNS records you need to publish: ```json { "success": true, "identity_type": "DOMAIN", "identity_value": "yourcompany.com", "status": "PENDING", "dns_records": { "spf": { "type": "TXT", "hostname": "yourcompany.com", "value": "v=spf1 include:_spf.mail.splashifypro.com ~all" }, "dkim": { "type": "CNAME", "hostname": "splashify._domainkey.yourcompany.com", "value": "splashify._domainkey.mail.splashifypro.com" }, "dmarc": { "type": "TXT", "hostname": "_dmarc.yourcompany.com", "value": "v=DMARC1; p=quarantine; rua=mailto:dmarc@splashifypro.com" } } } ``` Publish all three records on your DNS provider and trigger a re-check: ```bash curl -X POST https://api.splashifypro.com/api/v1/partner/email/identities/DOMAIN/yourcompany.com/verify \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` When `"status": "VERIFIED"` comes back, you can send from any address ending in `@yourcompany.com`. ## Step 3 — Send a transactional email ```bash curl https://api.splashifypro.com/api/v1/partner/email/send \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": "hello@yourcompany.com", "to": ["customer@example.com"], "subject": "Welcome", "html_body": "

Welcome aboard 👋

Thanks for signing up.

", "text_body": "Welcome aboard. Thanks for signing up." }' ``` The response carries the `message_id` you can use to poll delivery status: ```json { "success": true, "results": [ { "recipient": "customer@example.com", "message_id": "550e8400-e29b-41d4-a716-446655440000", "status": "queued" } ] } ``` ## Step 4 — Watch delivery status Poll the message status: ```bash curl https://api.splashifypro.com/api/v1/partner/email/emails/550e8400-e29b-41d4-a716-446655440000 \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` Or — better — set up a [webhook](/webhooks) that we POST to whenever the message transitions through `Send → Delivery → Open → Click / Bounce / Complaint`. ## Step 5 — Move out of sandbox New accounts start in **sandbox mode**: - 200 emails/day cap - 1 email/sec peak send rate - Can only send to verified-recipient addresses To go live, request **production access**: ```bash curl -X POST https://api.splashifypro.com/api/v1/partner/email/production-access \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "use_case": "Transactional emails for our SaaS application: signup confirmations, password resets, payment receipts.", "email_volume_estimate": "5000-15000 per day", "has_unsubscribe_method": true, "has_consent_proof": true }' ``` Approved requests lift sandbox + bump your daily quota to 50,000 + peak rate to 14/sec. Most requests are reviewed within 24 business hours. ## Next steps - [**Authentication →**](/getting-started/authentication) — API key best practices - [**Concepts →**](/concepts) — configuration sets, event destinations, suppression - [**Webhooks →**](/webhooks) — real-time delivery events - [**API Reference →**](/api-reference) — full endpoint reference ======================================================================== ## Authentication URL: https://partner-docs.splashifypro.com/getting-started/authentication ======================================================================== # Authentication The Splashify Pro Email API uses Bearer-token authentication. Every request must carry a valid API key in the `Authorization` header. ## Generating an API key 1. Log in to [partner.splashifypro.com](https://partner.splashifypro.com) 2. Navigate to **Settings → API Keys** 3. Click **Generate API Key** 4. Copy the key — it is shown **once**. Lose it and you'll need to regenerate. API keys carry the prefix `pk_live_` and are 64 characters long. ## Using your key Set the `Authorization` header on every request: ```http Authorization: Bearer pk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ``` cURL: ```bash curl https://api.splashifypro.com/api/v1/partner/email/quotas \ -H "Authorization: Bearer pk_live_..." ``` Node: ```js fetch("https://api.splashifypro.com/api/v1/partner/email/quotas", { headers: { Authorization: `Bearer ${process.env.SPLASHIFY_API_KEY}` }, }); ``` Python: ```python r = requests.get( "https://api.splashifypro.com/api/v1/partner/email/quotas", headers={"Authorization": f"Bearer {os.environ['SPLASHIFY_API_KEY']}"}, ) ``` ## Rate limits API keys are rate-limited per partner account, not per key. Defaults: - **Sandbox:** 1 send/sec, 200 sends/day - **Production:** 14 sends/sec (configurable per partner), 50,000 sends/day (configurable per partner) Rate-limit responses come back as `429 Too Many Requests`. Retry with exponential backoff. ## Key security best practices - **Never embed in client-side code.** API keys go on your server, never in browser JS, mobile apps, or public repos. - **Use environment variables.** Most CI / hosting platforms support secret env vars. `.env` files should be `.gitignore`'d. - **Rotate periodically.** Regenerate keys every 90 days at minimum. - **Use one key per environment.** Separate keys for staging / production make blast-radius cleanup easier. ## Revoking a compromised key 1. Go to **Settings → API Keys** 2. Find the compromised key 3. Click **Revoke** Revocation is immediate. New requests with the revoked key get `401 Unauthorized` within ~5 seconds. ## Authentication errors | Status | Code | Cause | |---|---|---| | 401 | `MISSING_AUTH` | No `Authorization` header | | 401 | `INVALID_KEY_FORMAT` | Key doesn't match `pk_live_...` | | 401 | `KEY_NOT_FOUND` | Key was revoked or never existed | | 401 | `KEY_INACTIVE` | Account suspended | | 403 | `IP_BLOCKED` | Caller's IP is on your account's IP allowlist | ======================================================================== ## cURL Quickstart URL: https://partner-docs.splashifypro.com/getting-started/curl-quickstart ======================================================================== # cURL Quickstart The fastest way to test an integration. Every endpoint can be hit directly with cURL — useful for debugging, scripts, and CI smoke tests. ## Set your API key ```bash export SPLASHIFY_API_KEY="pk_live_..." ``` ## Send an email ```bash curl https://api.splashifypro.com/api/v1/partner/email/send \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": "hello@yourcompany.com", "to": ["customer@example.com"], "subject": "Welcome", "html_body": "

Welcome 👋

", "text_body": "Welcome." }' ``` ## Verify a domain ```bash curl https://api.splashifypro.com/api/v1/partner/email/identities \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{"identity_type": "DOMAIN", "identity_value": "yourcompany.com"}' ``` ## Check delivery status ```bash curl https://api.splashifypro.com/api/v1/partner/email/emails/$MESSAGE_ID \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ## Get send statistics ```bash curl 'https://api.splashifypro.com/api/v1/partner/email/stats?days=14' \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` Pipe through `jq` to format: ```bash curl ... | jq '.stats[] | {date: .day_bucket, sent: .send_count, bounced: .bounced_count}' ``` ======================================================================== ## Go Quickstart URL: https://partner-docs.splashifypro.com/getting-started/go-quickstart ======================================================================== # Go Quickstart ```go package main "bytes" "encoding/json" "fmt" "net/http" "os" ) func main() { body, _ := json.Marshal(map[string]any{ "from": "hello@yourcompany.com", "to": []string{"customer@example.com"}, "subject": "Welcome", "html_body": "

Welcome 👋

", "text_body": "Welcome.", }) req, _ := http.NewRequest("POST", "https://api.splashifypro.com/api/v1/partner/email/send", bytes.NewReader(body), ) req.Header.Set("Authorization", "Bearer "+os.Getenv("SPLASHIFY_API_KEY")) req.Header.Set("Content-Type", "application/json") res, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer res.Body.Close() var out struct { Success bool `json:"success"` Results []struct { MessageID string `json:"message_id"` Status string `json:"status"` } `json:"results"` } json.NewDecoder(res.Body).Decode(&out) fmt.Println(out.Results[0].MessageID) } ``` ## What's next - [Verify your domain](/api-reference/identities/create) - [Set up webhooks](/webhooks) ======================================================================== ## Node.js Quickstart URL: https://partner-docs.splashifypro.com/getting-started/node-quickstart ======================================================================== # Node.js Quickstart Send transactional and marketing email from Node.js. Works on every runtime — Node 18+, Bun, Deno, and edge environments (Vercel, Cloudflare Workers, etc.). ## 1. Install We don't ship a Node SDK yet — use any HTTP client. `fetch` is built into Node 18+. ```bash # No install needed if you're on Node 18+ ``` ## 2. Set your API key ```bash export SPLASHIFY_API_KEY="pk_live_..." ``` ## 3. Send your first email ```js const res = await fetch( "https://api.splashifypro.com/api/v1/partner/email/send", { method: "POST", headers: { "Authorization": `Bearer ${process.env.SPLASHIFY_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ from: "hello@yourcompany.com", to: ["customer@example.com"], subject: "Welcome to our app", html_body: "

Welcome 👋

Your account is ready.

", text_body: "Welcome. Your account is ready.", }), }, ); const data = await res.json(); console.log(data.results[0].message_id); ``` ## 4. Handle errors ```js if (!res.ok) { const err = await res.json(); switch (err.error) { case "INVALID_REQUEST": // Bad input — read err.message break; case "FROM_NOT_VERIFIED": // Verify your domain at /identities first break; case "SUPPRESSED_RECIPIENT": // Recipient is on your suppression list break; case "INSUFFICIENT_BALANCE": // Recharge your wallet break; default: console.error(err.message); } } ``` ## 5. With React Email [React Email](https://react.email) renders HTML email from React components. You author your template as JSX, render it server-side, and pass the HTML to `/send`. ```bash npm install @react-email/components @react-email/render ``` ```jsx function Welcome({ name }) { return ( Hi {name}, ); } const html = render(); await fetch("https://api.splashifypro.com/api/v1/partner/email/send", { method: "POST", headers: { "Authorization": `Bearer ${process.env.SPLASHIFY_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ from: "hello@yourcompany.com", to: ["customer@example.com"], subject: "Welcome", html_body: html, }), }); ``` ## What's next - [Verify your domain](/api-reference/identities/create) — required before sending in production - [Set up webhooks](/webhooks) — listen to delivery events - [Use templates](/api-reference/templates/create) — reusable bodies with variables - [Send in bulk](/api-reference/emails/send-bulk) — up to 500 recipients per request ======================================================================== ## PHP Quickstart URL: https://partner-docs.splashifypro.com/getting-started/php-quickstart ======================================================================== # PHP Quickstart ```php true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . getenv('SPLASHIFY_API_KEY'), 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'from' => 'hello@yourcompany.com', 'to' => ['customer@example.com'], 'subject' => 'Welcome', 'html_body' => '

Welcome 👋

', 'text_body' => 'Welcome.', ]), ]); $res = curl_exec($ch); $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $body = json_decode($res, true); if ($status >= 400) { throw new RuntimeException($body['error'] . ': ' . $body['message']); } echo $body['results'][0]['message_id']; ``` ## Laravel In `config/services.php`: ```php 'splashify' => ['key' => env('SPLASHIFY_API_KEY')], ``` ```php use Illuminate\Support\Facades\Http; Http::withToken(config('services.splashify.key')) ->post('https://api.splashifypro.com/api/v1/partner/email/send', [ 'from' => 'hello@yourcompany.com', 'to' => ['customer@example.com'], 'subject' => 'Welcome', 'html_body' => '

Welcome

', ]) ->throw(); ``` ## What's next - [Verify your domain](/api-reference/identities/create) - [Set up webhooks](/webhooks) ======================================================================== ## Python Quickstart URL: https://partner-docs.splashifypro.com/getting-started/python-quickstart ======================================================================== # Python Quickstart ```bash pip install requests export SPLASHIFY_API_KEY="pk_live_..." ``` ```python r = requests.post( "https://api.splashifypro.com/api/v1/partner/email/send", headers={ "Authorization": f"Bearer {os.environ['SPLASHIFY_API_KEY']}", "Content-Type": "application/json", }, json={ "from": "hello@yourcompany.com", "to": ["customer@example.com"], "subject": "Welcome to our app", "html_body": "

Welcome 👋

", "text_body": "Welcome.", }, ) r.raise_for_status() print(r.json()["results"][0]["message_id"]) ``` ## Handling errors ```python if not r.ok: err = r.json() raise RuntimeError(f"{err['error']}: {err['message']}") ``` ## Async (httpx) ```python async def send(): async with httpx.AsyncClient() as c: r = await c.post( "https://api.splashifypro.com/api/v1/partner/email/send", headers={"Authorization": f"Bearer {API_KEY}"}, json={...}, ) return r.json() asyncio.run(send()) ``` ## What's next - [Verify your domain](/api-reference/identities/create) - [Set up webhooks](/webhooks) - [Use templates](/api-reference/templates/create) ======================================================================== ## Quick Start URL: https://partner-docs.splashifypro.com/getting-started/quick-start ======================================================================== # Quick Start Send your first email in under 60 seconds. Try the form below — the request fires against your real API key on save. Welcome 👋

Your account is ready.

", text_body: "Welcome. Your account is ready." }} pathParams={[]} /> ## What happens on send ```mermaid sequenceDiagram participant You as Your app participant API as Splashify API participant MX as Recipient MX participant Hook as Your webhook You->>API: POST /partner/email/send API-->>You: 200 { message_id, status: queued } API->>MX: STARTTLS + DKIM-signed message MX-->>API: 250 OK API->>Hook: POST event (Send) API->>Hook: POST event (Delivery) Note over MX,Hook: Bounce / Complaint events follow async ``` The API responds immediately with a `message_id`. Behind the scenes we: 1. **Look up your verified identity.** From-address must be on a verified domain (or be a verified email address). 2. **Check the suppression list.** Recipients on your account's suppression list are rejected with `status: rejected` — no SMTP attempt is made and you're not billed. 3. **Deduct ₹0.01 from your wallet.** First 200/day are free in sandbox. 4. **Sign with DKIM** using a key that resolves through your domain's CNAME at `splashify._domainkey.`. 5. **Connect to the recipient MX over STARTTLS.** 6. **Emit events** — `Send`, then `Delivery` (or `Bounce` / `Complaint`), then `Open` / `Click` if the recipient engages. ## Try it from your terminal ```bash export SPLASHIFY_API_KEY="pk_live_..." curl https://api.splashifypro.com/api/v1/partner/email/send \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": "hello@yourcompany.com", "to": ["customer@example.com"], "subject": "Welcome", "html_body": "

Hi 👋

Welcome aboard.

" }' ``` ## What's next - **Verify your domain →** [Identities](/api-reference/identities/create) - **Set up webhooks →** [Webhooks](/webhooks) - **Use templates →** [Templates](/api-reference/templates/create) - **Bulk send →** [SendBulk](/api-reference/emails/send-bulk) - **Move out of sandbox →** [Production access](/api-reference/production-access/submit) ======================================================================== ## SMTP Relay URL: https://partner-docs.splashifypro.com/getting-started/smtp-quickstart ======================================================================== # SMTP Relay For frameworks and platforms that can't easily call our REST API, use the SMTP relay. Same DKIM signing, suppression, billing, and webhook delivery — your app just speaks plain SMTP. ## Connection | Setting | Value | |---|---| | Host | `smtp.splashifypro.com` | | Port | `587` (STARTTLS) or `465` (TLS) | | Username | `emailapikey` (literal) | | Password | `pk_live_...` (your API key) | TLS is required. Plain-text auth is rejected. ## Test with swaks ```bash swaks --to customer@example.com \ --from hello@yourcompany.com \ --server smtp.splashifypro.com \ --port 587 -tls \ --auth-user emailapikey \ --auth-password "$SPLASHIFY_API_KEY" \ --header "Subject: Test from swaks" \ --body "Hello" ``` ## WordPress (WP Mail SMTP) 1. Plugin → **WP Mail SMTP** → install 2. Mailer → **Other SMTP** 3. Host: `smtp.splashifypro.com` · Port: `587` · Encryption: `TLS` 4. Auth: ON · Username: `emailapikey` · Password: your API key 5. Save & send a test email ## Django ```python # settings.py EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" EMAIL_HOST = "smtp.splashifypro.com" EMAIL_PORT = 587 EMAIL_USE_TLS = True EMAIL_HOST_USER = "emailapikey" EMAIL_HOST_PASSWORD = os.environ["SPLASHIFY_API_KEY"] DEFAULT_FROM_EMAIL = "hello@yourcompany.com" ``` ## Laravel ```env MAIL_MAILER=smtp MAIL_HOST=smtp.splashifypro.com MAIL_PORT=587 MAIL_USERNAME=emailapikey MAIL_PASSWORD=${SPLASHIFY_API_KEY} MAIL_ENCRYPTION=tls MAIL_FROM_ADDRESS=hello@yourcompany.com ``` ## Node (nodemailer) ```js const transport = nodemailer.createTransport({ host: "smtp.splashifypro.com", port: 587, secure: false, // STARTTLS auth: { user: "emailapikey", pass: process.env.SPLASHIFY_API_KEY, }, }); await transport.sendMail({ from: "hello@yourcompany.com", to: "customer@example.com", subject: "Hello", html: "

Welcome

", }); ``` ## Postfix In `/etc/postfix/main.cf`: ``` relayhost = [smtp.splashifypro.com]:587 smtp_sasl_auth_enable = yes smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd smtp_sasl_security_options = noanonymous smtp_use_tls = yes smtp_tls_security_level = encrypt ``` In `/etc/postfix/sasl_passwd`: ``` [smtp.splashifypro.com]:587 emailapikey:pk_live_... ``` ```bash postmap /etc/postfix/sasl_passwd chmod 600 /etc/postfix/sasl_passwd* postfix reload ``` ## Supabase Auth (transactional email for signups, OTPs, password reset) Supabase ships a "Custom SMTP" panel that you can point at the relay so signup confirmation, magic-link, and password-recovery emails come from your verified sender instead of the Supabase default. **Project → Project Settings → Auth → SMTP Settings:** | Field | Value | |---|---| | Sender email | `noreply@yourcompany.com` *(must be on a verified identity)* | | Sender name | Whatever appears in the inbox From line | | Host | `smtp.splashifypro.com` | | Port | `587` | | Username | `emailapikey` | | Password | your `pk_live_…` API key | | Minimum interval per user | `60` seconds (Supabase default — anti-abuse) | Then save & hit **Send test email**. If you get `535 5.0.0 5.7.0 invalid api key format` it means you pasted something other than a `pk_live_…` (or `sk_live_…` for app-developer accounts) key — copy the key from [Settings → API key](https://partner.splashifypro.com/settings) on the partner dashboard. ## Things to know - **Sender must be on a verified identity.** Sends from an unverified email/domain are rejected with `550 5.7.1 sender '' is not on a verified identity for this partner`. Add the domain or email at [Settings → Identities](https://partner.splashifypro.com/identities) before testing. - **Suppression list applies.** Recipients on your account suppression list are rejected at RCPT TO with `550 5.7.1 recipient on suppression list`. - **Free sandbox: 200 emails/day.** New accounts are sandboxed. Once you exceed the 200/day quota you'll get `452 4.7.0 sandbox daily quota of 200 emails reached`. Request production access from the dashboard to lift it. - **Billing.** ₹0.01/email for paid sends (post-sandbox); sandbox sends are free. SMTP and REST sends bill identically. - **Webhooks fire normally.** SMTP and REST sends produce the same event stream into your configured destinations. - **TLS is required.** Plain-text AUTH is rejected; the relay only advertises AUTH after STARTTLS (port 587) or on the implicit-TLS port (465). - **Per-message metadata via headers.** Set `X-Configuration-Set: ` on the outbound message to attribute the send to a config set. ======================================================================== ## API Reference URL: https://partner-docs.splashifypro.com/api-reference ======================================================================== # API Reference The Splashify Pro Email API is RESTful, accepts JSON-encoded request bodies, returns JSON-encoded responses, and uses standard HTTP response codes + verbs. ## Base URL ``` https://api.splashifypro.com/api/v1/partner/email ``` All endpoints documented in this reference are relative to this base URL. ## Authentication Bearer token in the `Authorization` header: ```http Authorization: Bearer pk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ``` See [Authentication](/getting-started/authentication) for key generation, rotation, and rate-limit details. ## Request format POST + PATCH + PUT bodies are JSON. Set `Content-Type: application/json`. Field names are `snake_case`. Required fields raise `400 INVALID_REQUEST` when missing. ## Response format Every response is JSON. Successful responses include `success: true`: ```json { "success": true, "data": { ... } } ``` Errors include a stable `error` code + a human-readable `message`: ```json { "success": false, "error": "FROM_NOT_VERIFIED", "message": "From address is not on a verified identity." } ``` See [Errors](/api-reference/errors) for the full list. ## Resources | Resource | Endpoints | |---|---| | **[Emails](/api-reference/emails/send)** | `/send`, `/send-raw`, `/send-template`, `/send-bulk`, `/emails/:id` | | **[Templates](/api-reference/templates/list)** | CRUD + preview | | **[Identities](/api-reference/identities/list)** | List, create, get, verify, delete | | **[Configuration Sets](/api-reference/configuration-sets/list)** | CRUD + multi-tenant attribution | | **[Event Destinations](/api-reference/event-destinations/list)** | Webhook destinations attached to config sets | | **[Suppression](/api-reference/suppression/list)** | List, get, put, delete | | **[Quotas & Stats](/api-reference/quotas/get-quota)** | Send quota, statistics, reputation, events | | **[Production Access](/api-reference/production-access/submit)** | Sandbox → production lifecycle | ## Versioning The API is versioned in the URL — `/api/v1/`. We will not introduce breaking changes within a version. New optional fields, new endpoints, and new event types may land at any time. If we ever ship `/api/v2/`, we'll keep `/api/v1/` operational for at least 12 months and announce the deprecation timeline at least 90 days in advance. ## Idempotency Send endpoints are NOT idempotent — duplicate calls produce duplicate sends. Implement application-level dedup against your business identifier (charge_id, signup_id, etc.) before calling `/send`. Idempotency-key support via `Idempotency-Key` header is on the roadmap. ======================================================================== ## Create configuration set URL: https://partner-docs.splashifypro.com/api-reference/configuration-sets/create ======================================================================== # Create configuration set ```http POST /api/v1/partner/email/configuration-sets ``` ## Request body ```json { "name": "production", "description": "Production transactional sends", "customer_id": "cust_550e8400-...", "sending_enabled": true, "reputation_tracking_enabled": true, "suppression_options": "BOUNCE_AND_COMPLAINT", "tags": { "env": "prod", "team": "platform" } } ``` | Field | Type | Required | Notes | |---|---|---|---| | `name` | string | yes | Unique per partner, alphanumeric + `_` `-`, 1-256 chars | | `description` | string | no | | | `customer_id` | uuid | no | Downstream end-customer attribution | | `sending_enabled` | bool | no | Default `true` | | `reputation_tracking_enabled` | bool | no | Default `true` | | `suppression_options` | string | no | `NONE` / `BOUNCE` / `COMPLAINT` / `BOUNCE_AND_COMPLAINT` (default) | | `custom_redirect_domain` | string | no | Per-config-set click-tracking host | | `tags` | object | no | Arbitrary key-value pairs surfaced on `mail.tags` in webhooks | ## Response ```json { "success": true, "configuration_set": { "config_set_id": "cs_550e8400-...", "name": "production", ... } } ``` ## Common errors | Status | Code | Meaning | |---|---|---| | 409 | `CONFIG_SET_NAME_TAKEN` | Another set with that name exists | | 400 | `INVALID_REQUEST` | Bad `suppression_options` value or missing `name` | ======================================================================== ## Delete configuration set URL: https://partner-docs.splashifypro.com/api-reference/configuration-sets/delete ======================================================================== # Delete configuration set Cascades — deletes the configuration set + every event destination attached to it. ```http DELETE /api/v1/partner/email/configuration-sets/:id ``` ## cURL ```bash curl -X DELETE \ https://api.splashifypro.com/api/v1/partner/email/configuration-sets/cs_550e8400-... \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ## What this does NOT do - Doesn't delete the historical events / stats associated with this config set — they're still accessible via `GET /events` and `GET /stats?config_set_id=...` - Doesn't suppress in-flight sends. New sends referencing the deleted set's name return `404 CONFIG_SET_NOT_FOUND`. ## Reactivating Create a new config set with the same `name`. It gets a fresh `config_set_id` — old `message_id` records still reference the old id but new sends use the new one. ======================================================================== ## Get configuration set URL: https://partner-docs.splashifypro.com/api-reference/configuration-sets/get ======================================================================== # Get configuration set ```http GET /api/v1/partner/email/configuration-sets/:id ``` ## Path parameters | Field | Notes | |---|---| | `id` | The `config_set_id` returned from `POST /configuration-sets` | ## Response ```json { "success": true, "configuration_set": { "config_set_id": "cs_550e8400-...", "name": "production", "description": "...", "sending_enabled": true, "reputation_tracking_enabled": true, "suppression_options": "BOUNCE_AND_COMPLAINT", "tags": {"env": "prod"}, "created_at": "...", "updated_at": "..." } } ``` ## cURL ```bash curl https://api.splashifypro.com/api/v1/partner/email/configuration-sets/cs_550e8400-... \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ======================================================================== ## List configuration sets URL: https://partner-docs.splashifypro.com/api-reference/configuration-sets/list ======================================================================== # List configuration sets ```http GET /api/v1/partner/email/configuration-sets ``` ## Query parameters | Field | Type | Notes | |---|---|---| | `customer_id` | uuid | Filter by downstream customer attribution | ## Response ```json { "success": true, "configuration_sets": [ { "config_set_id": "cs_550e8400-...", "name": "production", "description": "Production transactional sends", "sending_enabled": true, "reputation_tracking_enabled": true, "suppression_options": "BOUNCE_AND_COMPLAINT", "tags": {"env": "prod"}, "created_at": "2026-05-03T12:00:00Z", "updated_at": "2026-05-03T12:00:00Z" } ], "count": 1 } ``` ## cURL ```bash curl https://api.splashifypro.com/api/v1/partner/email/configuration-sets \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ======================================================================== ## Update configuration set URL: https://partner-docs.splashifypro.com/api-reference/configuration-sets/update ======================================================================== # Update configuration set ```http PATCH /api/v1/partner/email/configuration-sets/:id ``` All fields are optional — pass only what you want to change. Common use: kill-switch a set with `sending_enabled: false`, toggle suppression behaviour, rename. ## Request body ```json { "name": "production-v2", "sending_enabled": false, "suppression_options": "BOUNCE", "tags": { "env": "prod", "version": "v2" } } ``` | Field | Type | Notes | |---|---|---| | `name` | string | Must remain unique per partner | | `description` | string | | | `sending_enabled` | bool | Disable to halt new sends through this set | | `reputation_tracking_enabled` | bool | | | `suppression_options` | string | `NONE` / `BOUNCE` / `COMPLAINT` / `BOUNCE_AND_COMPLAINT` | | `custom_redirect_domain` | string | | | `tags` | object | Replaces existing tags entirely (not merge) | ## cURL ```bash curl -X PATCH \ https://api.splashifypro.com/api/v1/partner/email/configuration-sets/cs_550e8400-... \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{"sending_enabled": false}' ``` ======================================================================== ## Apply for RCS URL: https://partner-docs.splashifypro.com/api-reference/customers/apply-rcs ======================================================================== # Apply for RCS Submit the RCS Business Messaging KYC for a customer you have already created via [Create customer](/api-reference/customers/create). The admin team uses this submission to register a Google RCS sender (agent) for that customer. ```http POST /api/v1/partner/customers/{customer_id}/rcs/apply ``` The call is **always treated as a submission** — the record flips to `pending` immediately. Use the panel UI's `PUT …/kyc` endpoint with `save_as_draft: true` if you need draft saves. All document fields are URLs. Host the files on your own CDN and pass public URLs in the body — the API does not accept multipart uploads on this endpoint (the panel UI uses a separate `POST …/kyc/upload` helper for that). ## Request body ```json { "registered_company_name": "Acme Pvt. Ltd.", "company_hq_location": "Mumbai, India", "corporate_registration_url": "https://cdn.acme.com/docs/coi.pdf", "company_gstin": "27ABCDE1234F1Z5", "electricity_bill_url": "https://cdn.acme.com/docs/elec-april.pdf", "brand_support_phone": "+919876543210", "support_email": "support@acme.com", "company_address": "Floor 5, Some Tower, Andheri East", "company_zip": "400069", "transaction_type": "domestic", "optin_video_url": "https://drive.google.com/file/d/...", "bot_message_type": "Transactional", "bot_name": "Acme Helpdesk", "brand_name": "Acme", "bot_logo_url": "https://cdn.acme.com/brand/logo-224.jpg", "short_description": "Order updates, OTPs, and customer support.", "banner_image_url": "https://cdn.acme.com/brand/banner-1440.jpg", "primary_phone_number": "+919876543210", "phone_label": "Support", "brand_website": "https://acme.com", "website_label": "Visit our site", "agent_email": "rcs@acme.com", "email_label": "Email us", "terms_url": "https://acme.com/terms", "privacy_url": "https://acme.com/privacy", "languages": ["English", "Hindi"], "brand_details_brand_name": "Acme", "industry_type": "Retail / E-commerce", "contact_first_name": "John", "contact_last_name": "Doe", "contact_designation": "Marketing Manager", "contact_email": "john@acme.com", "contact_mobile": "+919876543210" } ``` ### Fields #### Section 1 — Business overview | Field | Required | Notes | |---|---|---| | `registered_company_name` | yes | Legal name on the registration certificate | | `company_hq_location` | yes | City / state / country | | `corporate_registration_url` | yes | Public URL to the registration certificate (PDF or image) | | `company_gstin` | yes | India GSTIN; format is not enforced server-side | | `electricity_bill_url` | yes | Public URL to a recent electricity bill (PDF or image) | | `brand_support_phone` | yes | E.164 recommended | | `support_email` | yes | RFC-shape email | | `company_address` | yes | Free-form | | `company_zip` | yes | Free-form | | `transaction_type` | yes | `domestic` or `international` | #### Section 2 — RCS requirements | Field | Required | Notes | |---|---|---| | `optin_video_url` | yes | Public URL (Google Drive, S3, etc.) to a short video that demonstrates how end-users opt in to receive RCS messages from this brand | #### Section 3 — Agent creation | Field | Required | Notes | |---|---|---| | `bot_message_type` | yes | One of `OTP`, `Transactional`, `Promotional`, `MultiUse` | | `bot_name` | yes | ≤ 40 chars | | `brand_name` | yes | Public-facing brand | | `bot_logo_url` | yes | 224 × 224 JPEG, ≤ 50 KB | | `short_description` | yes | ≤ 100 chars | | `banner_image_url` | yes | 1440 × 448 JPEG, ≤ 200 KB | | `primary_phone_number` | yes | E.164 | | `phone_label` | yes | ≤ 25 chars | | `brand_website` | yes | URL | | `website_label` | yes | ≤ 25 chars | | `agent_email` | yes | URL | | `email_label` | yes | ≤ 25 chars | | `terms_url` | yes | URL | | `privacy_url` | yes | URL | | `languages` | yes | At least one supported language (e.g. `["English", "Hindi"]`) | #### Section 4 — Brand details | Field | Required | Notes | |---|---|---| | `brand_details_brand_name` | yes | | | `industry_type` | yes | | | `contact_first_name` | yes | | | `contact_last_name` | yes | | | `contact_designation` | yes | | | `contact_email` | yes | RFC-shape email | | `contact_mobile` | yes | E.164 recommended | ## Response — 202 ```json { "success": true, "message": "RCS KYC submitted. Admin will review and notify you.", "customer_id": "8f3b2a1e-49d8-4c2d-9e1a-b7e6d5f8a3c2", "status": "pending", "submitted_at": "2026-05-20T10:18:42Z" } ``` The submission is queued for admin review. Poll [Check RCS status](/api-reference/customers/rcs-status) (or wait for the admin to email you). Re-posting after an admin **rejection** is supported — the new payload replaces the previous one and flips status back to `pending`. While the status is `pending` or `approved`, a re-post still overwrites the row silently, so guard the call in your integration if you don't want that. ## Errors | Status | Reason | Response | |---|---|---| | `400` | Missing / invalid field | `{ "success": false, "message": "Field \"bot_name\" is required" }` | | `400` | Length constraint | `{ "success": false, "message": "short_description must be ≤ 100 characters" }` | | `400` | Invalid `customer_id` | `{ "success": false, "message": "Invalid customer_id" }` | | `401` | Missing or invalid partner auth | `{ "success": false, "message": "unauthorized" }` | | `404` | `customer_id` doesn't belong to this partner | `{ "success": false, "message": "Customer not found under this partner" }` | | `500` | Database error | `{ "success": false, "message": "Failed to submit RCS KYC" }` | ## cURL ```bash curl -X POST \ https://api.splashifypro.com/api/v1/partner/customers/$CUSTOMER_ID/rcs/apply \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d @rcs-kyc.json ``` ## Notes - **Authentication** — same as the rest of the partner API: send your partner API key as `Authorization: Bearer `. - **File hosting** — the panel uploads files for you and stores them on Splashify-managed DO Spaces. For this API endpoint you host the files yourself; we only store the URLs. URLs must be publicly fetchable (Google checks them during agent verification). - **Review SLA** — typical admin review is 1–2 business days. Approval triggers a webhook (see [Webhooks](/webhooks)) and the status endpoint flips to `approved`. ======================================================================== ## Create customer URL: https://partner-docs.splashifypro.com/api-reference/customers/create ======================================================================== # Create customer Create a customer record in your partner roster. Each customer is identified by a `customer_id` and carries the basic contact info (full name, email, phone) used by the rest of the RCS solution to address messages and bind sender configuration. Email and phone are **unique per partner** — a duplicate POST returns `409` with the `field` that collided. ```http POST /api/v1/partner/customers ``` ## Request body ```json { "full_name": "Acme Corporation", "email": "contact@acme.com", "phone": "+919876543210" } ``` | Field | Type | Required | Notes | |---|---|---|---| | `full_name` | string | yes | Customer name; minimum 2 characters | | `email` | string | yes | RFC-shape email; stored lowercased + trimmed for the uniqueness check | | `phone` | string | yes | 7–15 digits, optional leading `+` for E.164. Visual separators (spaces, dashes, parens, dots) are stripped before the uniqueness check, so `"(98765) 432-10"` and `"9876543210"` collide | ## Response — 201 ```json { "success": true, "message": "Customer created successfully", "customer_id": "8f3b2a1e-49d8-4c2d-9e1a-b7e6d5f8a3c2", "customer": { "customer_id": "8f3b2a1e-49d8-4c2d-9e1a-b7e6d5f8a3c2", "full_name": "Acme Corporation", "email": "contact@acme.com", "phone": "+919876543210", "created_at": "2026-05-20T10:14:33Z" } } ``` Pin the `customer_id` in your own record — every subsequent RCS call (sender config, message sends, conversation lookups) references it. ## Errors | Status | Reason | Response | |---|---|---| | `400` | Missing or invalid field | `{ "success": false, "message": "valid email required" }` | | `401` | Missing or invalid partner auth | `{ "success": false, "message": "unauthorized" }` | | `409` | Email already on the roster | `{ "success": false, "message": "a customer with this email already exists", "field": "email" }` | | `409` | Phone already on the roster | `{ "success": false, "message": "a customer with this phone already exists", "field": "phone" }` | | `500` | Database error | `{ "success": false, "message": "database error" }` | ## cURL ```bash curl -X POST \ https://api.splashifypro.com/api/v1/partner/customers \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "full_name": "Acme Corporation", "email": "contact@acme.com", "phone": "+919876543210" }' ``` ## Notes - **Authentication** — same as the rest of the partner API: send your partner API key as `Authorization: Bearer `. - **Idempotency** — POST is *not* idempotent. Calling it twice with the same email or phone returns `409` on the second call. If you need retry-safety, look up the customer by email first (a list endpoint is on the roadmap). - **Normalisation** — the server normalises email (`lowercase + trim`) and phone (digits + optional leading `+`) before checking uniqueness and before storing. ======================================================================== ## Check RCS status URL: https://partner-docs.splashifypro.com/api-reference/customers/rcs-status ======================================================================== # Check RCS status Return the current RCS KYC status for a customer, including the rejection reason and review metadata when present. Designed for cheap polling — only the status fields are returned, not the full document. ```http GET /api/v1/partner/customers/{customer_id}/rcs/status ``` ## Response — 200 (submission exists) ```json { "success": true, "customer_id": "8f3b2a1e-49d8-4c2d-9e1a-b7e6d5f8a3c2", "status": "approved", "rejection_reason": "", "reviewed_by": "ops@evolvepro.tech", "submitted_at": "2026-05-20T10:18:42Z", "reviewed_at": "2026-05-21T07:04:11Z" } ``` ## Response — 200 (no submission yet) ```json { "success": true, "customer_id": "8f3b2a1e-49d8-4c2d-9e1a-b7e6d5f8a3c2", "status": "not_applied", "message": "No RCS KYC submitted for this customer yet." } ``` ### Status values | `status` | Meaning | |---|---| | `not_applied` | No submission exists for this customer — call [Apply for RCS](/api-reference/customers/apply-rcs) | | `draft` | Partner panel saved a draft; not yet submitted for review | | `pending` | Submitted; awaiting admin review | | `approved` | Admin approved — RCS sender is being registered (or already is) | | `rejected` | Admin rejected — see `rejection_reason`; re-call apply with a corrected payload | `submitted_at` and `reviewed_at` are zero-valued (`"0001-01-01T00:00:00Z"`) when they haven't happened yet. ## Errors | Status | Reason | Response | |---|---|---| | `400` | Invalid `customer_id` | `{ "success": false, "message": "Invalid customer_id" }` | | `401` | Missing or invalid partner auth | `{ "success": false, "message": "unauthorized" }` | | `404` | `customer_id` doesn't belong to this partner | `{ "success": false, "message": "Customer not found under this partner" }` | | `500` | Database error | `{ "success": false, "message": "Failed to load status" }` | ## cURL ```bash curl -X GET \ https://api.splashifypro.com/api/v1/partner/customers/$CUSTOMER_ID/rcs/status \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ## Notes - **Polling cadence** — admin reviews are typically 1–2 business days, so polling more than once every few minutes is wasteful. Prefer webhooks (on the roadmap) where possible. - **Caching** — the endpoint hits Scylla directly with no caching; safe to call frequently but please don't. ======================================================================== ## Send RCS message URL: https://partner-docs.splashifypro.com/api-reference/customers/send-rcs ======================================================================== # Send RCS message Send an RCS message to one of your customer's contacts. Authenticate with your Splashify partner API key (`Authorization: Bearer pk_live_…`, the same key as the rest of the partner API). The request body mirrors the standard RCS message shape — text, rich card, carousel, or media, with optional interactive suggestions. ```http POST /api/v1/partner/customers/{customer_id}/rcs/send ``` Behind the scenes we pick the right RCS sender for the customer and auto-prefix `phone_no` with `+` if you forgot the country-code prefix. The upstream RCS provider's response (status code + body) is returned to you unchanged. ## Prerequisites - `customer_id` must belong to your partner account. - The customer must be **RCS-Active**. This is set up once by your admin team after KYC approval; if it's not done yet the endpoint returns 400. ## Common fields | Field | Type | Required | Notes | |---|---|---|---| | `type` | string | yes | One of `text`, `card`, `multiple_cards`, `media` | | `phone_no` | string | yes | E.164 (e.g. `+919876543210`). We auto-prefix `+` if you send the bare digits | | `extra` | string | no | Free-form passthrough metadata; surfaces on the status callback | | `suggestions` | array | no | Interactive buttons (see below) | The sender identity is bound to the `customer_id` in the URL — you do not need to (and cannot) override it in the body. ## 1. Plain text ```json { "type": "text", "phone_no": "+919876543210", "text": "Your order #12345 has shipped." } ``` `text` supports up to **2500 chars**. ## 2. Text with suggestions ```json { "type": "text", "phone_no": "+919876543210", "text": "Handy suggestions", "suggestions": [ { "type": "view_location", "text": "Our store", "latitude": 19.076090, "longitude": 72.877426, "postback": "store-mumbai" }, { "type": "dial", "text": "Call us", "call_to": "+917718963553", "postback": "support-line" }, { "type": "url", "text": "Track order", "url": "https://acme.com/orders/12345", "postback": "track" }, { "type": "message", "text": "Yes", "postback": "confirm-yes" } ] } ``` Up to **11 suggestions** per message (RM platform limit). ## 3. Rich card (standalone) ```json { "type": "card", "phone_no": "+919876543210", "extra": "order-12345", "card": { "title": "Your order has shipped", "description": "Tracking number RTML123. Expected delivery 24 May.", "url": "https://cdn.acme.com/orders/12345/banner.jpg", "suggestions": [ { "type": "url", "text": "Track", "url": "https://acme.com/track/12345", "postback": "track" } ] } } ``` | Card field | Limit | |---|---| | `title` | ≤ 200 chars | | `description` | ≤ 2000 chars | | `url` | Public HTTPS image / media URL | | `card.suggestions` | Same shape as top-level `suggestions` | ## 4. Rich card carousel ```json { "type": "multiple_cards", "phone_no": "+919876543210", "cards": [ { "card": { "title": "Card 1", "description": "First card body", "url": "https://cdn.acme.com/c1.jpg" }, "suggestions": [ { "type": "message", "text": "Hello", "postback": "greet" } ] }, { "card": { "title": "Card 2", "description": "Second card body", "url": "https://cdn.acme.com/c2.jpg" }, "suggestions": [ { "type": "dial", "text": "Call", "call_to": "+917718963553", "postback": "call" } ] } ] } ``` Up to **10 cards** in a single carousel. ## 5. Media (PDF / image / video) ```json { "type": "media", "phone_no": "+919876543210", "url": "https://cdn.acme.com/invoice-12345.pdf" } ``` Media types Route Mobile accepts: - `image/jpeg`, `image/png` - `video/mp4` - `application/pdf` ## Media with URL suggestion ```json { "type": "media", "phone_no": "+919876543210", "url": "https://cdn.acme.com/preview.jpg", "extra": "campaign-summer", "fallback_text": "Open https://acme.com/summer for the full deal", "suggestions": [ { "type": "url", "text": "Shop now", "url": "https://acme.com/summer", "postback": "summer-cta" } ] } ``` ## Response On success the upstream provider returns: ```json { "message": "Message request has been created", "id": "9f8d32c0-1f60-11f0-8d0c-0a58a9fedgrc02" } ``` Pin the `id` — it appears on every subsequent status callback (`rcs.status.sent` / `delivered` / `read` / `failed`). See [RCS Incoming Events](/webhooks/rcs-incoming) for the webhook shape. ## Errors | Status | Example | |---|---| | `400` | `{ "success": false, "message": "RCS not active for this customer." }` — ask your admin to complete RCS activation | | `400` | `{ "status": "error", "description": "Missing phone number" }` — payload validation from the upstream provider | | `401` | `{ "success": false, "message": "unauthorized" }` — your partner API key is missing or invalid | | `401` / `403` | `{ "success": false, "code": "rcs_sender_unavailable", "message": "RCS sender temporarily unavailable for this customer. Try again in a few minutes; if it persists, contact support." }` | | `404` | `{ "success": false, "message": "customer not found" }` | | `500` | `{ "status": "error", "description": "Internal server error" }` | | `502` | `{ "success": false, "message": "Upstream RCS provider request failed: " }` (network / DNS / timeout) | | `503` | `{ "success": false, "message": "RCS sender not yet configured for this customer." }` | ## cURL ```bash curl -X POST \ "https://api.splashifypro.com/api/v1/partner/customers/$CUSTOMER_ID/rcs/send" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "text", "phone_no": "+919876543210", "text": "Your order has shipped." }' ``` ## Notes - **Authentication.** Use your Splashify partner API key — `Authorization: Bearer pk_live_…`, the same key you use for the rest of the partner API. - **Idempotency.** Every POST is treated as a new send. If you retry on a network error you may get duplicate messages. Put your own idempotency key in `extra` and dedupe on the status callbacks. - **Suggestion limit.** A maximum of 11 suggestions per message envelope; carousels share that budget across all cards. - **Phone normalisation.** We trim and prepend `+` if missing. We do not guess the country code — a number without one is rejected. ======================================================================== ## Get email status URL: https://partner-docs.splashifypro.com/api-reference/emails/get-status ======================================================================== # Get email status Check whether a sent email has been queued, delivered, bounced, or rejected. ```http GET /api/v1/partner/email/emails/:message_id ``` ## Path parameters | Field | Notes | |---|---| | `message_id` | UUID returned from `/send` / `/send-template` / `/send-bulk` / `/send-raw` | ## Response ```json { "success": true, "message_id": "550e8400-e29b-41d4-a716-446655440000", "status": "delivered", "from_address": "hello@yourcompany.com", "to_address": "customer@example.com", "subject": "Welcome", "category": "transactional", "smtp_message_id": "", "created_at": "2026-05-03T12:34:56Z", "sent_at": "2026-05-03T12:34:57Z", "delivered_at": "2026-05-03T12:34:57Z" } ``` ## Status values | Status | Meaning | |---|---| | `queued` | API accepted; not yet delivered to recipient MX | | `sent` | Sent to recipient MX (collapsed with `delivered` for our pipeline) | | `delivered` | Recipient MX returned 250 OK | | `bounced` | Hard or soft-bounce-exhausted bounce | | `complained` | Recipient marked as spam (FBL report received) | | `rejected` | Refused pre-SMTP (suppression list, sandbox cap, etc.) | | `failed` | Soft bounce retries exhausted | ## Bounce details When `status: bounced`: ```json { "status": "bounced", "bounced_at": "2026-05-03T12:35:01Z", "bounce_type": "Permanent", "bounce_reason": "no_such_user" } ``` ## When to use polling vs webhooks **Polling is the wrong default.** Webhooks (set up via [event destinations](/api-reference/event-destinations/create)) push status changes to your server within seconds without you polling. Use this endpoint for: - One-off lookups (debugging a specific send) - UI surfaces showing live status (after a webhook update lands) - Reconciliation jobs (check that webhook delivery wasn't dropped) Don't poll every send every 5 seconds — wasteful for both sides. ## Common errors | Status | Code | Meaning | |---|---|---| | 404 | `MESSAGE_NOT_FOUND` | Message id doesn't match any send. Possible: wrong account, malformed id, or message older than retention window | ## Retention Outbox rows are retained for 30 days. After that, older rows return 404. For long-term audit, listen to the `Send` / `Delivery` / `Bounce` webhook events and persist them yourself. ======================================================================== ## Send email URL: https://partner-docs.splashifypro.com/api-reference/emails/send ======================================================================== # Send email Send an email to one or more recipients. Equivalent to AWS SES `SendEmail`. ```http POST /api/v1/partner/email/send ``` Welcome 👋", text_body: "Welcome.", }} pathParams={[]} /> ## Request body | Field | Type | Required | Notes | |---|---|---|---| | `from` | string | yes | Sender address. Must be on a verified [identity](/api-reference/identities/create). Format: `"name "` or just `addr@example.com` | | `to` | string[] | yes | Up to 50 recipients (combined `to` + `cc` + `bcc`) | | `cc` | string[] | no | Carbon copy | | `bcc` | string[] | no | Blind carbon copy | | `reply_to` | string | no | Sets the `Reply-To:` header | | `subject` | string | yes | Subject line | | `html_body` | string | conditional | HTML body. One of `html_body` or `text_body` is required | | `text_body` | string | conditional | Plaintext body. Auto-derived from HTML if omitted | | `configuration_set_name` | string | no | Routes events to this config set's destinations | | `customer_id` | string (uuid) | no | Optional partner-side downstream customer attribution | | `category` | string | no | `transactional` (default) or `marketing` | | `tags` | object | no | Arbitrary key-value pairs that ride on `mail.tags` in webhooks | ## Response ```json { "success": true, "results": [ { "recipient": "customer@example.com", "message_id": "550e8400-e29b-41d4-a716-446655440000", "status": "queued" } ] } ``` `results` carries one entry per recipient — `to` + `cc` + `bcc` are flattened. | Field | Notes | |---|---| | `recipient` | Lowercase + trimmed | | `message_id` | Use this on `GET /emails/:message_id` to poll status | | `status` | `queued` or `rejected` | | `reason` | Present when `status: rejected` — typically `"suppressed"` | ## cURL ```bash curl https://api.splashifypro.com/api/v1/partner/email/send \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": "hello@yourcompany.com", "to": ["customer@example.com"], "subject": "Welcome", "html_body": "

Welcome

", "text_body": "Welcome." }' ``` ## Node ```js const res = await fetch( "https://api.splashifypro.com/api/v1/partner/email/send", { method: "POST", headers: { Authorization: `Bearer ${process.env.SPLASHIFY_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ from: "hello@yourcompany.com", to: ["customer@example.com"], subject: "Welcome", html_body: "

Welcome

", }), } ); const data = await res.json(); ``` ## Python ```python r = requests.post( "https://api.splashifypro.com/api/v1/partner/email/send", headers={"Authorization": f"Bearer {os.environ['SPLASHIFY_API_KEY']}"}, json={ "from": "hello@yourcompany.com", "to": ["customer@example.com"], "subject": "Welcome", "html_body": "

Welcome

", }, ) ``` ## Attachments `/send` accepts an `attachments[]` array of base64-encoded files. PDFs, images, documents, spreadsheets, ZIPs are all supported. ```json { "from": "billing@yourcompany.com", "to": ["customer@example.com"], "subject": "Your invoice", "html_body": "

Invoice attached.

", "attachments": [ { "filename": "invoice.pdf", "content_type": "application/pdf", "content_base64": "" } ] } ``` **Caps:** 20 files, 10 MiB total per message. Executable / script extensions (`.exe`, `.bat`, `.js`, `.ps1`, `.sh`, …) are blocked. See [Send with attachments](/api-reference/emails/send-with-attachments) for the full reference, inline-image (CID) handling, and per-language code samples. ## Common errors | Status | Code | Meaning | |---|---|---| | 400 | `INVALID_REQUEST` | Missing required field; read `message` | | 400 | `INVALID_ATTACHMENT` | Attachment failed validation — see [Send with attachments](/api-reference/emails/send-with-attachments) | | 400 | `FROM_NOT_VERIFIED` | Verify your domain at `POST /identities` | | 400 | `TOO_MANY_RECIPIENTS` | Split into multiple sends | | 402 | `INSUFFICIENT_BALANCE` | Recharge wallet | | 403 | `SANDBOX_QUOTA_REACHED` | Daily 200/day cap hit — request production access | | 403 | `SENDING_PAUSED` | Account paused — contact support | | 429 | `RATE_LIMITED` | Per-second cap exceeded — back off | ======================================================================== ## Send bulk URL: https://partner-docs.splashifypro.com/api-reference/emails/send-bulk ======================================================================== # Send bulk Send a single template to up to 500 recipients across 50 destinations, each with their own variable map. Equivalent to AWS `SendBulkTemplatedEmail`. ```http POST /api/v1/partner/email/send-bulk ``` ## Request body ```json { "from": "hello@yourcompany.com", "template_name": "newsletter-june", "default_template_data": { "campaign": "june-2026" }, "destinations": [ { "to": ["alex@example.com"], "replacement_data": { "first_name": "Alex" } }, { "to": ["brett@example.com"], "replacement_data": { "first_name": "Brett" } } ], "configuration_set_name": "marketing" } ``` | Field | Type | Required | Notes | |---|---|---|---| | `from` | string | yes | Verified identity | | `template_name` | string | yes | | | `default_template_data` | object | no | Variables that apply to every recipient unless overridden | | `destinations[]` | array | yes | Up to **50 destinations** per request | | `destinations[].to` | string[] | yes | Up to **50 recipients per destination** | | `destinations[].cc` | string[] | no | | | `destinations[].bcc` | string[] | no | | | `destinations[].replacement_data` | object | no | Per-destination variable overrides — merged onto `default_template_data` | | `reply_to` | string | no | | | `configuration_set_name` | string | no | | | `customer_id` | string | no | | | `category` | string | no | `transactional` (default) or `marketing` | | `tags` | object | no | | ## Limits - 50 destinations per request - 50 recipients per destination - 500 recipients total per request For larger lists, paginate into multiple `/send-bulk` calls. ## Response ```json { "success": true, "results": [ { "destination": 0, "recipient": "alex@example.com", "message_id": "550e8400-...", "status": "queued" }, { "destination": 1, "recipient": "brett@example.com", "message_id": "660e8400-...", "status": "queued" } ] } ``` `results[].destination` is the zero-based index into your `destinations[]` array — so you can correlate per-recipient outcomes back to the destination you submitted. ## Variable resolution For each destination: 1. Start with `default_template_data` 2. Merge `destinations[i].replacement_data` on top (per-destination overrides win) 3. Substitute `{{key}}` tokens in subject + html_body + text_body ## Per-destination 4xx behaviour If one destination fails (e.g. recipient on suppression list): - That destination gets `status: rejected` + `reason` - Other destinations continue processing If a 5xx fires (template-load failure, etc.): - The entire bulk aborts - `failed_at` in the response indicates where we stopped - Already-processed destinations stay queued ## cURL ```bash curl https://api.splashifypro.com/api/v1/partner/email/send-bulk \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": "hello@yourcompany.com", "template_name": "newsletter", "destinations": [ {"to": ["a@example.com"], "replacement_data": {"name": "A"}}, {"to": ["b@example.com"], "replacement_data": {"name": "B"}} ] }' ``` ======================================================================== ## Send raw URL: https://partner-docs.splashifypro.com/api-reference/emails/send-raw ======================================================================== # Send raw Send a fully-formed MIME message (RFC 5322). For when you need control over headers, attachments, multipart structure, or DKIM-signed forwarding. Equivalent to AWS SES `SendRawEmail`. ```http POST /api/v1/partner/email/send-raw ``` ## Request body ```json { "raw_message_base64": "RnJvbTogaGVsbG9AeW91cmNvbXBhbnkuY29tDQpUbzogY3VzdG9tZXJAZXhhbXBsZS5jb20NClN1YmplY3Q6IEhlbGxvDQpDb250ZW50LVR5cGU6IHRleHQvcGxhaW47IGNoYXJzZXQ9dXRmLTgNCg0KSGVsbG8h", "configuration_set_name": "production" } ``` | Field | Type | Required | Notes | |---|---|---|---| | `raw_message_base64` | string | yes | base64-encoded RFC 5322 message. Max 10 MB decoded. | | `from` | string | no | Override From-address parsing. Defaults to parsing the message's `From:` header | | `to` | string[] | no | Override recipient parsing. Defaults to parsing `To:` header | | `configuration_set_name` | string | no | | | `customer_id` | string | no | | | `category` | string | no | | | `tags` | object | no | | ## What you control - **All headers** — From, To, Cc, Bcc, Subject, Reply-To, Message-ID, arbitrary `X-` headers, `In-Reply-To`, `References` - **MIME structure** — multipart/alternative, multipart/mixed, multipart/related (inline images via `Content-ID:`) - **Attachments** — encode each part with the right Content-Type + Content-Disposition + Content-Transfer-Encoding - **Custom headers** — anything not on the reserved list ## What we override - `Date:` — set to send time if missing - `Message-ID:` — added if missing - DKIM-Signature — we always sign with our shared key - Return-Path — set to our bounce address for VERP routing ## When to use this - Sending forwarded mail with original headers preserved - Replying inside an existing thread (`In-Reply-To` / `References`) - Multipart with attachments - Custom List-Id / List-Help / List-Subscribe / List-Unsubscribe headers For simple HTML+text sends, use [`/send`](/api-reference/emails/send) — much less ceremony. ## Example Building a multipart message in Node: ```js const msg = createMimeMessage(); msg.setSender("hello@yourcompany.com"); msg.setRecipient("customer@example.com"); msg.setSubject("Receipt + invoice attached"); msg.addMessage({ contentType: "text/plain", data: "See attached." }); msg.addMessage({ contentType: "text/html", data: "

See attached.

" }); msg.addAttachment({ filename: "invoice.pdf", contentType: "application/pdf", data: pdfBase64, }); const raw = msg.asEncoded(); const rawB64 = Buffer.from(raw).toString("base64"); await fetch("https://api.splashifypro.com/api/v1/partner/email/send-raw", { method: "POST", headers: { Authorization: `Bearer ${API_KEY}` }, body: JSON.stringify({ raw_message_base64: rawB64 }), }); ``` ======================================================================== ## Send template URL: https://partner-docs.splashifypro.com/api-reference/emails/send-template ======================================================================== # Send template Send a previously-saved [template](/api-reference/templates/create) with per-recipient `{{variables}}` substituted in the subject + body. ```http POST /api/v1/partner/email/send-template ``` ## Request body ```json { "from": "hello@yourcompany.com", "to": ["customer@example.com"], "template_name": "welcome", "variables": { "first_name": "Alex", "company_name": "Acme" }, "configuration_set_name": "production" } ``` | Field | Type | Required | Notes | |---|---|---|---| | `from` | string | yes | Verified identity | | `to` | string[] | yes | Up to 50 recipients | | `cc` / `bcc` | string[] | no | | | `reply_to` | string | no | | | `template_name` | string | yes | Friendly name from `POST /templates` | | `variables` | object | no | `{{key}}` tokens replaced in subject + body | | `configuration_set_name` | string | no | Event-routing scope | | `customer_id` | string | no | Downstream customer attribution | | `category` | string | no | `transactional` (default) or `marketing` | ## Response Same shape as [`/send`](/api-reference/emails/send). ## Variable substitution `{{first_name}}` and `{{ first_name }}` (with spaces) both resolve. Missing variables stay as-is in the rendered output — they don't raise errors. To enforce required variables, declare them on the template via `declared_vars`. ## cURL ```bash curl https://api.splashifypro.com/api/v1/partner/email/send-template \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": "hello@yourcompany.com", "to": ["customer@example.com"], "template_name": "welcome", "variables": {"first_name": "Alex"} }' ``` ## Common errors | Status | Code | Meaning | |---|---|---| | 404 | `TEMPLATE_NOT_FOUND` | `template_name` not registered for this account | | 400 | `RENDERING_FAILURE` | Template rendering failed (rare — most variables are forgiving) | ======================================================================== ## Send with attachments URL: https://partner-docs.splashifypro.com/api-reference/emails/send-with-attachments ======================================================================== # Send with attachments Three of the four send endpoints accept an `attachments[]` array — `/send`, `/send-template`, and `/send-bulk`. Each attachment carries the file bytes inline as base64; we build the `multipart/mixed` MIME structure for you, DKIM-sign it, and ship it to the recipient through the same pipeline as plain-body emails. `/send-raw` always supported attachments because partners build the MIME themselves — see [Send raw](/api-reference/emails/send-raw) for that path. ## Attachment object Each entry in `attachments[]` is shaped like this: ```json { "filename": "invoice-2026-04.pdf", "content_type": "application/pdf", "content_base64": "", "content_id": "logo-header", "inline": false } ``` | Field | Type | Required | Meaning | |---|---|---|---| | `filename` | string | yes | Displayed by the recipient's mail client. Auto-sanitized — directory separators + control chars stripped. | | `content_type` | string | optional | MIME type (e.g. `application/pdf`). When omitted, we infer from the filename extension. Falls back to `application/octet-stream`. | | `content_base64` | string | yes | Standard base64 encoding of the file bytes. **Not** URL-safe base64 — use the standard alphabet. | | `content_id` | string | optional | Sets `Content-ID` so HTML can reference the attachment as ``. Pair with `inline: true`. | | `inline` | boolean | optional | When `true`, sets `Content-Disposition: inline` (image renders in the body). When `false` (default), sets `attachment` (file appears in the recipient's attachments list). | ## Limits | Cap | Value | What happens when exceeded | |---|---|---| | Files per message | **20** | Returns `400 INVALID_ATTACHMENT` with `"max 20 attachments per message"` | | Total decoded bytes | **10 MiB** | Returns `400 INVALID_ATTACHMENT` with the bytes-cap message. Sum runs across all attachments — a single 10.1 MiB PDF is rejected just like 11 × 1 MiB files. | | Filename length | 256 chars | Rejected at validation. | | Content-Type length | 256 chars | Rejected at validation. | ## Blocked file types Executable + script extensions are refused at the API layer because recipient mail providers (Gmail, Outlook, corporate gateways) block them outright — sending one would cost a wallet deduction for a guaranteed bounce. Blocked extensions: `.exe` `.bat` `.cmd` `.com` `.scr` `.msi` `.vbs` `.vbe` `.js` `.jse` `.wsf` `.wsh` `.ps1` `.dll` `.jar` `.app` `.sh` `.pl` `.py` `.lnk` Workaround: bundle them inside a `.zip`. ZIPs are allowed. ## Example — single PDF ```bash ATT=$(base64 -i invoice.pdf | tr -d '\n') curl https://api.splashifypro.com/api/v1/partner/email/send \ -H "Authorization: Bearer pk_live_…" \ -H "Content-Type: application/json" \ -d "{ \"from\": \"billing@yourcompany.com\", \"to\": [\"customer@example.com\"], \"subject\": \"Invoice for March\", \"html_body\": \"

Hi! Your invoice for March is attached.

\", \"attachments\": [ { \"filename\": \"invoice-march-2026.pdf\", \"content_type\": \"application/pdf\", \"content_base64\": \"$ATT\" } ] }" ``` ## Example — Node.js (multiple files) ```js const toAtt = (path, contentType) => ({ filename: path.split('/').pop(), content_type: contentType, content_base64: readFileSync(path).toString('base64'), }) const r = await fetch('https://api.splashifypro.com/api/v1/partner/email/send', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.SPLASHIFY_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ from: 'billing@yourcompany.com', to: ['customer@example.com'], subject: 'Your March documents', html_body: '

Attached: invoice + receipt + signed contract.

', attachments: [ toAtt('./invoice.pdf', 'application/pdf'), toAtt('./receipt.pdf', 'application/pdf'), toAtt('./contract.pdf', 'application/pdf'), ], }), }) console.log(await r.json()) ``` ## Example — Python ```python def att(path, ct): with open(path, 'rb') as f: return { 'filename': os.path.basename(path), 'content_type': ct, 'content_base64': base64.b64encode(f.read()).decode(), } r = requests.post( 'https://api.splashifypro.com/api/v1/partner/email/send', headers={'Authorization': f"Bearer {os.environ['SPLASHIFY_API_KEY']}"}, json={ 'from': 'support@yourcompany.com', 'to': ['customer@example.com'], 'subject': 'Attached photo', 'html_body': '

Here\'s the photo from your visit.

', 'attachments': [att('./photo.jpg', 'image/jpeg')], }, ) print(r.json()) ``` ## Example — Inline image (CID reference) When you want the image to render **inside** the email body instead of as a separate downloadable file, use `inline: true` + `content_id`, then reference it from your HTML with `cid:`: ```json { "from": "newsletter@yourcompany.com", "to": ["customer@example.com"], "subject": "Today's newsletter", "html_body": "

Welcome!

", "attachments": [ { "filename": "banner.png", "content_type": "image/png", "content_base64": "iVBORw0KGgo…", "content_id": "hero-banner", "inline": true } ] } ``` Most modern mail clients (Gmail web + mobile, Outlook, Apple Mail) render inline images correctly. A few hardened corporate gateways strip them — the file falls back to a regular attachment in those. ## Errors | Status | Code | Reason | |---|---|---| | 400 | `INVALID_ATTACHMENT` | Too many files, total size exceeded, blocked extension, invalid base64, or empty content | | 400 | `FROM_NOT_VERIFIED` | From-address not on a verified identity (same as plain send) | | 402 | `INSUFFICIENT_BALANCE` | Wallet too low to cover the per-recipient send cost | ## Pricing Attachments do **not** incur a per-byte charge. You're billed at the per-recipient rate (₹0.01 transactional / your tier rate for marketing) regardless of attachment size. The only practical cost is the 10 MiB total cap which limits how big each individual message can be. ## Best practices - **Compress where you can.** A PDF with images compressed to 200 dpi vs 600 dpi can be 5× smaller and still look perfect at on-screen resolution. Smaller messages deliver faster + use less of your 10 MiB budget. - **Use links for files > 5 MiB.** Recipient mail providers throttle large messages and some flag them as suspicious. For anything bigger, host on object storage and put a download link in the body. - **Set `content_type` explicitly when you can** — saves us a filename-extension guess. The mail client's "open with" picker uses this header. - **Prefer real filenames.** Generic `attachment.pdf` is allowed but hurts deliverability slightly (Gmail's spam filter weights template-shaped filenames). `invoice-march-2026.pdf` is better. ## Send-bulk semantics When you call `/send-bulk` with `attachments[]`, the same files are attached to **every** destination's email — there's no per-recipient attachment override. If you need different attachments per recipient, call `/send-template` once per recipient instead. ======================================================================== ## Errors URL: https://partner-docs.splashifypro.com/api-reference/errors ======================================================================== # API Errors Every error response from the Email API has the same shape: ```json { "success": false, "error": "INSUFFICIENT_BALANCE", "message": "Wallet balance ₹0.00 — recharge to continue sending." } ``` | Field | Meaning | |---|---| | `success` | Always `false` for errors | | `error` | Stable code — build retry/handling logic against this | | `message` | Human-readable text. May change for clarity over time | | `field` | (When relevant) Specific field that caused the error | For the complete error code reference + retry guidance, see [Knowledge Base → Errors](/knowledge-base/errors). ## HTTP status codes | Code | Meaning | |---|---| | `200` | Success | | `201` | Resource created | | `400` | Bad request — fix your inputs | | `401` | Missing / invalid API key | | `402` | Insufficient wallet balance — recharge | | `403` | Sandbox cap, sending paused, or feature locked | | `404` | Resource not found | | `409` | Conflict (duplicate name, etc.) | | `429` | Rate limit hit — back off | | `500` | Server error — retry with backoff | | `502` | Upstream gateway error (Zoho etc.) | | `503` | Database / dependency unavailable | ## Retry strategy | Status | Retry? | |---|---| | 4xx (except 408 / 429) | No — fix the request | | 408 / 429 | Yes — exponential backoff | | 5xx | Yes — up to 3 attempts with backoff | See [Knowledge Base → Errors](/knowledge-base/errors) for per-code guidance. ======================================================================== ## Create event destination URL: https://partner-docs.splashifypro.com/api-reference/event-destinations/create ======================================================================== # Create event destination Add a webhook destination to a configuration set. Events from sends referencing the config set will be POSTed to your URL with an HMAC-signed body. ```http POST /api/v1/partner/email/configuration-sets/:id/event-destinations ``` ## Request body ```json { "name": "production-webhook", "destination_type": "WEBHOOK", "webhook_url": "https://yourapp.com/webhooks/email", "webhook_secret": "your-shared-secret-min-16-chars", "matching_event_types": ["send", "delivered", "bounce", "complaint", "open", "click"], "enabled": true } ``` | Field | Type | Required | Notes | |---|---|---|---| | `name` | string | yes | Friendly identifier | | `destination_type` | string | no | `WEBHOOK` only (KAFKA, SNS — roadmap) | | `webhook_url` | string | yes | HTTPS endpoint. Must respond 2xx within 10s | | `webhook_secret` | string | no | HMAC-SHA256 signing key. **Saved write-only** — never echoed back | | `matching_event_types` | string[] | no | Subscribe to specific events. Empty = subscribe to everything | | `enabled` | bool | no | Default `true`. Set `false` to pause without deleting | Valid event types: - `send`, `delivered`, `bounce`, `complaint` - `open`, `click`, `reject` - `rendering_failure`, `delivery_delay` ## Response ```json { "success": true, "destination_id": "ed_550e8400-...", "name": "production-webhook", "webhook_url": "https://yourapp.com/webhooks/email", "matching_event_types": ["send", "delivered", "bounce", "complaint", "open", "click"], "enabled": true } ``` ## cURL ```bash curl https://api.splashifypro.com/api/v1/partner/email/configuration-sets/cs_.../event-destinations \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "production-webhook", "destination_type": "WEBHOOK", "webhook_url": "https://yourapp.com/webhooks/email", "webhook_secret": "...", "matching_event_types": ["send", "delivered", "bounce", "complaint", "open", "click"] }' ``` ## Common errors | Status | Code | Meaning | |---|---|---| | 400 | `INVALID_REQUEST` | Missing `webhook_url` or unknown event type | | 400 | `WEBHOOK_URL_INVALID` | URL must be HTTP(S) | ======================================================================== ## Delete event destination URL: https://partner-docs.splashifypro.com/api-reference/event-destinations/delete ======================================================================== # Delete event destination Removes a webhook destination from a configuration set. Future events for sends through this config set won't fan out to this URL. ```http DELETE /api/v1/partner/email/configuration-sets/:id/event-destinations/:dest_id ``` ## cURL ```bash curl -X DELETE \ https://api.splashifypro.com/api/v1/partner/email/configuration-sets/cs_.../event-destinations/ed_... \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ## What this does NOT do - Does NOT replay events your endpoint missed while the destination was misconfigured. Manual replay is on the roadmap. - Does NOT cancel in-flight retries — events that were queued before the delete may still attempt delivery for ~21 minutes (the retry window). ## Pause vs delete If you want to temporarily stop delivery: ```bash curl -X PATCH ... -d '{"enabled": false}' ``` is reversible. Deletion is not — re-creating gives you a fresh `destination_id` and the historical webhook-attempts audit trail no longer matches. ======================================================================== ## List event destinations URL: https://partner-docs.splashifypro.com/api-reference/event-destinations/list ======================================================================== # List event destinations ```http GET /api/v1/partner/email/configuration-sets/:id/event-destinations ``` Returns every webhook destination attached to a configuration set. ## Response ```json { "success": true, "event_destinations": [ { "destination_id": "ed_550e8400-...", "name": "production-webhook", "destination_type": "WEBHOOK", "webhook_url": "https://yourapp.com/webhooks/email", "is_signed": true, "matching_event_types": ["send", "delivered", "bounce", "complaint", "open", "click"], "enabled": true, "created_at": "2026-05-03T12:00:00Z", "updated_at": "2026-05-03T12:00:00Z" } ], "count": 1 } ``` `is_signed` is `true` when a `webhook_secret` is configured. The secret value itself is **never** returned by the API — to verify you have the right one, send a test event and check that your endpoint's HMAC validation passes. ## cURL ```bash curl https://api.splashifypro.com/api/v1/partner/email/configuration-sets/cs_.../event-destinations \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ======================================================================== ## Update event destination URL: https://partner-docs.splashifypro.com/api-reference/event-destinations/update ======================================================================== # Update event destination Modify a webhook destination — change the URL, rotate the signing secret, narrow/widen subscribed event types, or pause delivery. ```http PATCH /api/v1/partner/email/configuration-sets/:id/event-destinations/:dest_id ``` All fields optional — pass only what's changing. ## Request body ```json { "webhook_url": "https://yourapp.com/v2/webhooks/email", "webhook_secret": "rotated-secret-here", "matching_event_types": ["bounce", "complaint"], "enabled": true } ``` | Field | Type | Notes | |---|---|---| | `name` | string | | | `webhook_url` | string | HTTP(S) only | | `webhook_secret` | string | New value overwrites the old. API never echoes back | | `matching_event_types` | string[] | Replaces the existing list | | `enabled` | bool | Pause/resume delivery | ## Rotating the webhook secret Sequence: 1. Generate a new secret on your side (`openssl rand -hex 32`) 2. Update your endpoint to **accept both old and new** secrets for ~30 seconds 3. PATCH this endpoint with the new secret 4. Wait until in-flight requests drain (~30 seconds) 5. Remove old-secret support from your endpoint We don't echo old-or-new values, so the rotation window has to be coordinated by your code. ## cURL ```bash curl -X PATCH \ https://api.splashifypro.com/api/v1/partner/email/configuration-sets/cs_.../event-destinations/ed_... \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{"enabled": false}' ``` ======================================================================== ## Create identity URL: https://partner-docs.splashifypro.com/api-reference/identities/create ======================================================================== # Create identity Register a new sending identity. Returns the DNS records you need to publish (for `DOMAIN`) or a verification token (for `EMAIL_ADDRESS`). ```http POST /api/v1/partner/email/identities ``` ## Request body ```json { "identity_type": "DOMAIN", "identity_value": "yourcompany.com" } ``` | Field | Type | Required | Notes | |---|---|---|---| | `identity_type` | string | yes | `DOMAIN` or `EMAIL_ADDRESS` | | `identity_value` | string | yes | The domain (e.g. `yourcompany.com`) or full email address | ## Response For `DOMAIN`: ```json { "success": true, "identity_type": "DOMAIN", "identity_value": "yourcompany.com", "status": "PENDING", "dns_records": { "spf": {"type": "TXT", "hostname": "yourcompany.com", "value": "v=spf1 include:_spf.mail.splashifypro.com ~all"}, "dkim": {"type": "CNAME", "hostname": "splashify._domainkey.yourcompany.com", "value": "splashify._domainkey.mail.splashifypro.com"}, "dmarc": {"type": "TXT", "hostname": "_dmarc.yourcompany.com", "value": "v=DMARC1; p=quarantine; rua=mailto:dmarc@splashifypro.com"} } } ``` Publish all three records on your DNS provider, then call [`/verify`](/api-reference/identities/verify) to confirm. For `EMAIL_ADDRESS`: ```json { "success": true, "identity_type": "EMAIL_ADDRESS", "identity_value": "alerts@yourcompany.com", "status": "PENDING", "verification_token": "abc123..." } ``` We email a confirmation link to the address. Click it (or POST the token via `/verify`) to mark the identity verified. ## cURL ```bash curl https://api.splashifypro.com/api/v1/partner/email/identities \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{"identity_type": "DOMAIN", "identity_value": "yourcompany.com"}' ``` ## Common errors | Status | Code | Meaning | |---|---|---| | 400 | `INVALID_REQUEST` | Bad domain format / invalid email | | 400 | `PUBLIC_DOMAIN_BLOCKED` | Public-mail-provider domain (gmail.com, yahoo.com, etc.) — not allowed | | 400 | `IDENTITY_LIMIT_REACHED` | Sandbox account hit the 25-identity cap. The cap is **lifted automatically** once production access is approved — see below. | ### About the identity cap | Account state | Identity cap | |---|---| | **Sandbox** (default for new accounts) | **25** identities (domains + email addresses combined) | | **Production access approved** | **Unlimited** | Most accounts only need 1 – 5 identities even at full scale. Verifying a single domain (e.g. `mail.yourcompany.com`) authorizes every `@mail.yourcompany.com` to send, so you rarely need to register individual email addresses separately. #### Lifting the cap The cleanest path is **production access** — admin reviews your use case once, approves, and the identity cap is removed automatically along with the daily-send cap (200/day → 50,000/day) and peak-rate cap (1/sec → 14/sec). Submit at `POST /partner/email/production-access` or via the partner panel's **Production Access** tab. #### Working around the cap before production access If you're still in sandbox and genuinely need to register a new identity but already have 25: 1. List what's registered — `GET /partner/email/identities`. 2. Delete one you no longer send from — `DELETE /partner/email/identities/{type}/{value}`. 3. The slot frees immediately; re-try the create call. If your sandbox use case legitimately needs more than 25 identities (rare — usually means you're adding one `EMAIL_ADDRESS` per customer where verifying the parent domain would do), email support@splashifypro.in with the use case. ======================================================================== ## Delete identity URL: https://partner-docs.splashifypro.com/api-reference/identities/delete ======================================================================== # Delete identity Remove a sending identity. In-flight sends from that identity complete normally; new sends are rejected with `FROM_NOT_VERIFIED`. ```http DELETE /api/v1/partner/email/identities/:type/:value ``` ## cURL ```bash curl -X DELETE \ https://api.splashifypro.com/api/v1/partner/email/identities/DOMAIN/yourcompany.com \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ## Response ```json { "success": true } ``` ## What this does NOT do - Does NOT delete your DNS records — you can leave them published and re-add the identity later - Does NOT clear the suppression list — recipient suppressions are account-wide, not per-identity - Does NOT remove webhook destinations — those live on configuration sets, not identities ## Reactivating To re-add a deleted identity, just `POST /identities` again with the same value. If your DNS records are still published, the verification will pass on first check and the identity is back to `VERIFIED` immediately. ======================================================================== ## Get identity URL: https://partner-docs.splashifypro.com/api-reference/identities/get ======================================================================== # Get identity ```http GET /api/v1/partner/email/identities/:type/:value ``` Returns the current state of one identity, including a **live DNS-check overlay** so you see real-time `spf_ok` / `dkim_ok` / `dmarc_ok` flags reflecting what's actually published right now. ## Path parameters | Field | Notes | |---|---| | `type` | `DOMAIN` or `EMAIL_ADDRESS` | | `value` | The domain or email address | ## Response ```json { "success": true, "identity": { "identity_type": "DOMAIN", "identity_value": "yourcompany.com", "status": "VERIFIED", "spf_ok": true, "dkim_ok": true, "dmarc_ok": true, "dkim_selector": "splashify", "verified_at": "2026-05-03T12:00:00Z", "created_at": "2026-05-03T11:55:00Z", "dns_records": { "spf": {"pass": true, "found": "v=spf1 include:_spf.mail.splashifypro.com ~all", "expected": "..."}, "dkim": {"pass": true, "found": "CNAME splashify._domainkey.mail.splashifypro.com", "expected": "..."}, "dmarc": {"pass": true, "found": "v=DMARC1; p=quarantine; ...", "expected": "..."} } } } ``` ## cURL ```bash curl https://api.splashifypro.com/api/v1/partner/email/identities/DOMAIN/yourcompany.com \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ======================================================================== ## List identities URL: https://partner-docs.splashifypro.com/api-reference/identities/list ======================================================================== # List identities ```http GET /api/v1/partner/email/identities ``` Lists every sending identity (verified domains + email addresses) configured for your account. ## Response ```json { "success": true, "identities": [ { "identity_type": "DOMAIN", "identity_value": "yourcompany.com", "status": "VERIFIED", "spf_ok": true, "dkim_ok": true, "dmarc_ok": true, "verified_at": "2026-05-03T12:00:00Z", "created_at": "2026-05-03T11:55:00Z" } ], "count": 1 } ``` ## Status values | Status | Meaning | |---|---| | `PENDING` | DNS records not yet verified | | `VERIFIED` | All three records pass — ready to send | | `FAILED` | Verification failed multiple times — re-trigger via `POST /verify` | | `TEMPORARY_FAILURE` | DNS lookup error — retry | ## cURL ```bash curl https://api.splashifypro.com/api/v1/partner/email/identities \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ======================================================================== ## Verify identity URL: https://partner-docs.splashifypro.com/api-reference/identities/verify ======================================================================== # Verify identity Re-run DNS verification for a domain identity (after publishing records), or confirm an email-address identity (with the verification token). ```http POST /api/v1/partner/email/identities/:type/:value/verify ``` ## Domain verification ```bash curl -X POST \ https://api.splashifypro.com/api/v1/partner/email/identities/DOMAIN/yourcompany.com/verify \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` Response: ```json { "success": true, "status": "VERIFIED", "spf_ok": true, "dkim_ok": true, "dmarc_ok": true, "dns_records": { ... } } ``` When all three records pass, status flips to `VERIFIED`. If any record fails, status stays `PENDING` (or flips to `FAILED` after multiple unsuccessful attempts) — the per-record `pass` flags tell you which records to fix. ## Email-address verification ```bash curl -X POST \ https://api.splashifypro.com/api/v1/partner/email/identities/EMAIL_ADDRESS/alerts@yourcompany.com/verify \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{"token": "abc123..."}' ``` The token is the `verification_token` returned from [`POST /identities`](/api-reference/identities/create). ## Rate limiting We rate-limit re-verify calls to **once per 60 seconds per identity** to prevent DNS-server hammering. Calls inside the window return `429 RATE_LIMITED`. ## Auto-recheck behaviour We automatically re-check: - **Pending** identities every 1 hour for 7 days - **Verified** identities every 24 hours If a verified domain's records disappear, status flips back to `PENDING` and we email you. You can always trigger a manual re-check via this endpoint. ======================================================================== ## Pagination URL: https://partner-docs.splashifypro.com/api-reference/pagination ======================================================================== # Pagination List endpoints use cursor-based pagination via `limit` + `next_cursor` query parameters. ## Request ```bash curl 'https://api.splashifypro.com/api/v1/partner/email/identities?limit=50' \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` | Parameter | Type | Default | Notes | |---|---|---|---| | `limit` | int | 100 | Max 1000 | | `next_cursor` | string | — | Opaque cursor from a previous response | ## Response ```json { "success": true, "identities": [...], "count": 50, "next_cursor": "eyJpZCI6IjEyMzQ1IiwiX3QiOjE3..." } ``` `next_cursor` is opaque — do not parse it. Pass it back verbatim to fetch the next page: ```bash curl 'https://api.splashifypro.com/api/v1/partner/email/identities?limit=50&next_cursor=eyJ...' \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` When `next_cursor` is absent or empty, you've reached the end. ## Endpoints that paginate - `GET /identities` - `GET /configuration-sets` - `GET /templates` - `GET /suppression` - `GET /events` - `GET /production-access` ## Endpoints that don't - `GET /quotas`, `/stats`, `/reputation` — fixed-size responses - `GET /emails/:message_id` — single-row reads ## Default ordering Most list endpoints return newest-first. For deterministic ordering across pages, results are stable — re-fetching with the same cursor returns the same rows. ======================================================================== ## List production access requests URL: https://partner-docs.splashifypro.com/api-reference/production-access/list ======================================================================== # List production access requests Returns your full history of production-access submissions newest-first. ```http GET /api/v1/partner/email/production-access ``` ## Response ```json { "success": true, "requests": [ { "request_id": "req_550e8400-...", "use_case": "...", "email_volume_estimate": "5000 per day", "has_unsubscribe_method": true, "has_consent_proof": true, "domain_verified": true, "status": "APPROVED", "admin_notes": "Verified domain + clean use case. Approved for production.", "created_at": "2026-05-03T12:00:00Z", "reviewed_at": "2026-05-03T13:30:00Z" } ], "count": 1 } ``` ## Status values | Status | Meaning | |---|---| | `PENDING` | Awaiting review | | `APPROVED` | Lifted out of sandbox + quota raised | | `DENIED` | Resubmit after addressing `admin_notes` | ## cURL ```bash curl https://api.splashifypro.com/api/v1/partner/email/production-access \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ======================================================================== ## Submit production access request URL: https://partner-docs.splashifypro.com/api-reference/production-access/submit ======================================================================== # Submit production access request Request your account be moved out of sandbox into full production. Submission triggers an admin review. See [Sandbox vs Production](/knowledge-base/concepts/sandbox) for the review criteria. ```http POST /api/v1/partner/email/production-access ``` ## Request body ```json { "use_case": "We send transactional emails for our SaaS app — signup confirmations, password resets, payment receipts, weekly digest. Recipients are our paying customers who created accounts on our app.", "email_volume_estimate": "5000-15000 per day", "has_unsubscribe_method": true, "has_consent_proof": true } ``` | Field | Type | Required | Notes | |---|---|---|---| | `use_case` | string | yes | ≥30 chars. Real description of WHO you're sending to + WHY they signed up | | `email_volume_estimate` | string | yes | Realistic daily volume | | `has_unsubscribe_method` | bool | yes | Confirms you have unsubscribe links in marketing email | | `has_consent_proof` | bool | yes | Confirms recipients opted in (signup, double-opt-in, purchase, etc.) | | `legal_document` | file | no | Optional supporting document (PDF / PNG / JPEG, max 10 MB). See below | All four data fields are required. Faking them violates AUP — accounts that lie get downgraded. ## Optional legal document attachment You can attach a supporting compliance document — CAN-SPAM attestation, opt-in proof, signed contract, registered-business certificate, etc. — to speed up the review. Submissions that include a relevant document are typically approved faster because the reviewer doesn't need to ask follow-up questions. To attach a document, switch the request from JSON to `multipart/form-data` and include the file under the `legal_document` field. The file is uploaded to our DigitalOcean Spaces bucket and exposed to the admin reviewer via a public URL on the request row. **Limits:** PDF, PNG, or JPEG only. 10 MB max per file. ## Response ```json { "success": true, "request_id": "req_550e8400-...", "status": "PENDING", "domain_verified": true, "message": "Request submitted. Review typically takes 24 business hours.", "legal_document_url": "https://folder.splashifypro.com/partner_email_production_access/...pdf", "legal_document_filename": "compliance-attestation-2026.pdf" } ``` `domain_verified` reflects whether you currently have at least one domain identity in `VERIFIED` status — flagged on the admin side as a "ready for fast approval" signal. `legal_document_url` and `legal_document_filename` are returned only when a document was uploaded with the request. Both fields are omitted otherwise. ## Common errors | Status | Code | Meaning | |---|---|---| | 400 | `USE_CASE_TOO_SHORT` | Description must be ≥30 characters | | 400 | `MISSING_UNSUBSCRIBE_METHOD` | Required for marketing email | | 400 | `MISSING_CONSENT_PROOF` | Recipients must have opted in | | 400 | `INVALID_DOCUMENT_TYPE` | Document must be PDF, PNG, or JPEG | | 400 | `DOCUMENT_TOO_LARGE` | Document must be 10 MB or smaller | | 503 | `UPLOAD_UNAVAILABLE` | Spaces uploader is temporarily down — retry without the file | ## cURL — JSON (no document) ```bash curl https://api.splashifypro.com/api/v1/partner/email/production-access \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "use_case": "...", "email_volume_estimate": "5000 per day", "has_unsubscribe_method": true, "has_consent_proof": true }' ``` ## cURL — multipart with legal document ```bash curl https://api.splashifypro.com/api/v1/partner/email/production-access \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -F "use_case=We send transactional emails for our SaaS app..." \ -F "email_volume_estimate=5000 per day" \ -F "has_unsubscribe_method=true" \ -F "has_consent_proof=true" \ -F "legal_document=@./compliance-attestation.pdf" ``` Note the absence of `Content-Type` — `curl -F` sets the multipart boundary automatically. ======================================================================== ## Get quota URL: https://partner-docs.splashifypro.com/api-reference/quotas/get-quota ======================================================================== # Get quota Single read-once-per-page summary of your account's sending budget and reputation. ```http GET /api/v1/partner/email/quotas ``` ## Response ```json { "success": true, "quota": { "daily_send_quota": 50000, "peak_send_rate_per_second": 14, "sandbox": false, "sent_today": 1247, "sandbox_free_used_today": 0, "reputation_status": "HEALTHY", "reputation_bounce_rate": 0.0174, "reputation_complaint_rate": 0.0003, "sending_paused": false, "sending_paused_reason": "", "updated_at": "2026-05-03T12:00:00Z" } } ``` | Field | Notes | |---|---| | `daily_send_quota` | Max emails/day (200 in sandbox, 50K in production by default) | | `peak_send_rate_per_second` | Max sends/sec (1 in sandbox, 14 in production) | | `sandbox` | `true` until production access is approved | | `sent_today` | Emails sent today (UTC) | | `sandbox_free_used_today` | Emails consumed against the sandbox free tier | | `reputation_status` | `HEALTHY` / `AT_RISK` / `PAUSED` | | `reputation_bounce_rate` | Rolling 14-day bounce rate (0.0–1.0) | | `reputation_complaint_rate` | Rolling 14-day complaint rate (0.0–1.0) | | `sending_paused` | `true` if account is paused (admin or auto) | ## cURL ```bash curl https://api.splashifypro.com/api/v1/partner/email/quotas \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ======================================================================== ## Get reputation URL: https://partner-docs.splashifypro.com/api-reference/quotas/get-reputation ======================================================================== # Get reputation Returns the rolling 14-day bounce + complaint rate plus the classified reputation status. Equivalent to AWS SES `GetAccountReputation`. ```http GET /api/v1/partner/email/reputation ``` ## Response ```json { "success": true, "window_days": 14, "sent": 12500, "bounced": 218, "complained": 4, "bounce_rate": 0.01744, "complaint_rate": 0.00032, "status": "HEALTHY" } ``` ## Status thresholds ``` bounce > 10% OR complaint > 0.5% → PAUSED bounce > 5% OR complaint > 0.1% → AT_RISK else → HEALTHY ``` `PAUSED` triggers automatic sending halt — `/send` returns `403 SENDING_PAUSED`. See [Reputation](/knowledge-base/concepts/reputation) for recovery steps. ## cURL ```bash curl https://api.splashifypro.com/api/v1/partner/email/reputation \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ======================================================================== ## Get send statistics URL: https://partner-docs.splashifypro.com/api-reference/quotas/get-stats ======================================================================== # Get send statistics Day-by-day counters across the rolling window. Account-wide by default; pass `config_set_id` to scope to a specific configuration set. ```http GET /api/v1/partner/email/stats ``` ## Query parameters | Field | Type | Notes | |---|---|---| | `days` | int | 1-90, default 14 | | `config_set_id` | uuid | Scope to one config set instead of account-wide | ## Response ```json { "success": true, "stats": [ { "day_bucket": "2026-05-03", "send_count": 1247, "delivered_count": 1213, "bounced_count": 21, "complained_count": 2, "opened_count": 412, "clicked_count": 87, "rejected_count": 11 }, { "day_bucket": "2026-05-02", "send_count": 1102, ... } ], "days": 14 } ``` Days with zero sends are present in the array (with all counts zero) so you don't have to fill gaps client-side. ## cURL ```bash curl 'https://api.splashifypro.com/api/v1/partner/email/stats?days=30' \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` Per-config-set: ```bash curl 'https://api.splashifypro.com/api/v1/partner/email/stats?days=14&config_set_id=cs_...' \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ======================================================================== ## List events URL: https://partner-docs.splashifypro.com/api-reference/quotas/list-events ======================================================================== # List events Browse the recent event log. Useful for ad-hoc triage when you want to see what's happening without setting up a webhook. ```http GET /api/v1/partner/email/events ``` ## Query parameters | Field | Type | Notes | |---|---|---| | `day_bucket` | string | `YYYY-MM-DD` UTC. Default: today | | `event_type` | string | Filter by event type (lowercase) | | `limit` | int | 1-1000, default 200 | ## Response ```json { "success": true, "events": [ { "event_id": "e_550e8400-...", "event_type": "delivered", "message_id": "550e8400-...", "config_set_id": "cs_...", "recipient": "customer@example.com", "created_at": "2026-05-03T12:34:57Z" }, { "event_id": "e_660e8400-...", "event_type": "bounce", "message_id": "660e8400-...", "recipient": "deadbox@example.com", "bounce_type": "Permanent", "bounce_reason": "no_such_user", "created_at": "2026-05-03T12:35:01Z" }, { "event_id": "e_770e8400-...", "event_type": "click", "message_id": "...", "recipient": "...", "url": "https://yourapp.com/dashboard", "ip": "203.0.113.42", "ua": "Mozilla/5.0 ...", "created_at": "..." } ], "count": 3, "day_bucket": "2026-05-03" } ``` ## cURL ```bash curl 'https://api.splashifypro.com/api/v1/partner/email/events?day_bucket=2026-05-03&event_type=bounce&limit=100' \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ## Webhooks vs this endpoint For push-based event delivery, use [webhooks](/webhooks). This endpoint is for ad-hoc reads — debugging, auditing, replaying a window of events into your own database. Note: only the last 30 days of events are retained. For long-term archival, persist them at your end via webhooks. ======================================================================== ## RCS Panel API — Overview & Authentication URL: https://partner-docs.splashifypro.com/api-reference/rcs-panel ======================================================================== # RCS Panel API The RCS Panel (`rcs.splashifypro.com`) is the self-service surface for RCS-only customers — separate from the partner API and the app panel. If you signed up at the RCS Panel you can use this API to do everything the UI does: design templates, upload contact lists, run broadcasts, recharge your wallet, and pull dashboard stats. ```http Base URL: https://api.splashifypro.com Route: /api/v1/rcs/* Auth: Authorization: Bearer ``` ## Authentication RCS Panel auth is JWT-based and separate from the partner `pk_live_…` keys. You exchange your email + password for a JWT and pass it on every subsequent request. ### Sign in ```http POST /api/v1/rcs/auth/login Content-Type: application/json { "email": "you@yourbrand.com", "password": "your-password" } ``` Response: ```json { "success": true, "token": "eyJhbGciOiJIUzI1Ni...", "expires_in": 604800 } ``` The token is valid for **7 days**. Re-login any time before it expires; there is no refresh-token endpoint. ### Using the token All endpoints under `/api/v1/rcs/*` (except `/auth/*`) require: ```http Authorization: Bearer eyJhbGciOiJIUzI1Ni... ``` A missing or expired token returns `401` with `{"success": false, "message": "unauthorized"}`. ## Account status gates Some endpoints will return `403` until your account is fully provisioned: | State | Trigger | |---|---| | `pending` | Signed up, awaiting RCS sender provisioning | | `active` | Admin has assigned your bot name + credentials | | `suspend` | Account suspended by admin | Broadcasts are blocked until your account is `active` AND your wallet has enough balance to cover the first message. ## Errors Every error response is shaped as: ```json { "success": false, "message": "human-readable explanation" } ``` HTTP codes used: | Code | Meaning | |---|---| | 400 | Bad request (invalid body, missing field, garbage values) | | 401 | Missing / expired / invalid JWT | | 403 | Account state or wallet check failed | | 404 | Resource not found or not owned by you | | 409 | Conflict (e.g. starting a broadcast that's already running) | | 500 | Server error — open a support ticket with the response body | ## What's next - [Templates](./templates) — create + list + delete templates - [Contact lists](./contact-lists) — manage recipient lists - [Broadcasts](./broadcasts) — schedule and run sends - [Wallet & Recharge](./wallet) — Zoho Payments integration - [Invoices](./invoices) — list + download PDFs - [Dashboard stats](./stats) — KPIs and daily series ======================================================================== ## Broadcasts URL: https://partner-docs.splashifypro.com/api-reference/rcs-panel/broadcasts ======================================================================== # Broadcasts A broadcast pairs one **approved** template with one contact list. On creation we snapshot every list member into `rcs_broadcast_targets` (state `queued`), then the runner picks the broadcast up when its status is `running` (immediate) or `scheduled` + the scheduled time has passed. ```http POST /api/v1/rcs/broadcasts GET /api/v1/rcs/broadcasts GET /api/v1/rcs/broadcasts/{id} POST /api/v1/rcs/broadcasts/{id}/start POST /api/v1/rcs/broadcasts/{id}/cancel GET /api/v1/rcs/broadcasts/{id}/targets ``` ## Status lifecycle ``` draft → scheduled → running → completed ↘ ↘ cancelled failed ``` - **draft** — created but not yet sendable. Call `/start` to flip it. - **scheduled** — `scheduled_at` is in the future; the runner will pick it up when the time arrives. - **running** — actively sending. - **completed** — every target has settled (sent or failed). - **cancelled** — you called `/cancel`. Already-sent targets are not refunded. - **failed** — the runner couldn't proceed (no credentials, no balance for the first message, missing template). ## Create ```http POST /api/v1/rcs/broadcasts { "name": "May launch announcement", "template_id": "1f7a…", "list_id": "a1b2…", "scheduled_at": "2026-05-28T08:00:00Z" } ``` `scheduled_at` is optional. Omit it (or send a past timestamp) and the broadcast runs as soon as you call `/start`. The template must be in `effective_status = approved` — otherwise the endpoint returns 403. Response: ```json { "success": true, "broadcast_id": "b1b2…", "total_count": 18324, "status": "draft", "template_name": "order_shipped", "list_name": "May launch — opted in" } ``` ## Start ```http POST /api/v1/rcs/broadcasts/{id}/start ``` Returns `{ "success": true, "status": "running" }` (or `scheduled` if `scheduled_at` is still in the future). ## Cancel ```http POST /api/v1/rcs/broadcasts/{id}/cancel ``` Refuses with 409 if the broadcast is already in a terminal state. ## List + get ```http GET /api/v1/rcs/broadcasts GET /api/v1/rcs/broadcasts/{id} ``` Each row carries the live counters: ```json { "broadcast_id": "b1b2…", "name": "May launch announcement", "status": "running", "total_count": 18324, "sent_count": 8124, "delivered_count": 0, "read_count": 0, "failed_count": 12, "started_at": "2026-05-28T08:00:01Z" } ``` `delivered_count` and `read_count` stay at zero in v1 — delivery webhooks are wired into a follow-up release. `sent_count` updates in real time as the runner walks the list. ## Targets ```http GET /api/v1/rcs/broadcasts/{id}/targets?size=200 ``` Returns up to `size` recipient rows (default 200, max 1000): ```json { "success": true, "targets": [ { "recipient_phone": "+919876543210", "state": "sent", "rm_message_id": "rm_AbCd12…", "failure_reason": "", "sent_at": "2026-05-28T08:00:02Z", "delivered_at": "0001-01-01T00:00:00Z", "read_at": "0001-01-01T00:00:00Z" } ] } ``` `state` values: `queued`, `sent`, `delivered`, `read`, `failed`. ======================================================================== ## Contact lists URL: https://partner-docs.splashifypro.com/api-reference/rcs-panel/contact-lists ======================================================================== # Contact lists A contact list is a named bag of recipient phone numbers. Every broadcast picks one list and fans out to every member. ```http POST /api/v1/rcs/contact-lists GET /api/v1/rcs/contact-lists DELETE /api/v1/rcs/contact-lists/{id} POST /api/v1/rcs/contact-lists/{id}/members GET /api/v1/rcs/contact-lists/{id}/members DELETE /api/v1/rcs/contact-lists/{id}/members/{phone} ``` ## Create a list ```http POST /api/v1/rcs/contact-lists { "name": "May launch — opted in" } ``` Response: ```json { "success": true, "list_id": "a1b2…", "name": "May launch — opted in", "count": 0, "created_at": "2026-05-27T09:14:00Z" } ``` ## Add members (bulk) CSV-style uploads are passed as JSON. Phones are normalised — spaces, dashes, parens are stripped; a leading `+` is preserved. ```http POST /api/v1/rcs/contact-lists/{id}/members { "members": [ { "phone": "+919876543210", "name": "Asha", "extra": { "city": "Mumbai" } }, { "phone": "+919876543211", "name": "Rohan", "extra": { "city": "Pune" } } ] } ``` Up to **100,000 members per request**. Members already in the list overwrite (idempotent — safe to retry on partial network failure). Response: ```json { "success": true, "added": 2, "skipped": 0, "count": 2 } ``` `skipped` includes phones that failed normalisation (less than 7 digits after stripping) or were rejected by the database write. ## List members ```http GET /api/v1/rcs/contact-lists/{id}/members?size=200 ``` `size` defaults to 200, max 1000. For huge lists prefer slicing by phone prefix using the `extra` map at upload time. ## Delete a member ```http DELETE /api/v1/rcs/contact-lists/{id}/members/{phone} ``` `{phone}` is the URL-encoded full string including the `+`. ## Delete a list ```http DELETE /api/v1/rcs/contact-lists/{id} ``` Removes the list plus every member. Broadcasts that already snapshotted this list at create time keep running — they don't read the list table after the snapshot. ======================================================================== ## Invoices URL: https://partner-docs.splashifypro.com/api-reference/rcs-panel/invoices ======================================================================== # Invoices Every recharge generates a Zoho Billing invoice. We cache a thin row in `rcs_invoices` so the list endpoint is fast; the PDF is streamed from Zoho on demand. ```http GET /api/v1/rcs/invoices POST /api/v1/rcs/invoices/sync GET /api/v1/rcs/invoices/{id}/download ``` ## List (cached) ```http GET /api/v1/rcs/invoices ``` ```json { "success": true, "invoices": [ { "invoice_id": "1234567890", "invoice_number": "INV-001234", "status": "paid", "total": 5000.00, "balance": 0.00, "invoice_date": "2026-05-27", "due_date": "2026-05-27", "customer_id": "60005…", "customer_name": "Acme Corp", "currency_code": "INR", "currency_symbol": "₹", "synced_at": "2026-05-27T08:31:24Z" } ] } ``` ## Force re-sync ```http POST /api/v1/rcs/invoices/sync ``` Pulls every invoice for your Zoho customer record and upserts. Returns the freshly-cached list so a UI can drop the GET → POST → GET dance and just take the response of /sync directly. ## Download PDF ```http GET /api/v1/rcs/invoices/{id}/download ``` Streams `application/pdf` with `Content-Disposition: attachment` and the invoice number as the filename. Returns 404 if the invoice doesn't belong to your account (we check against the local cache so this is fast). ======================================================================== ## Send RCS message URL: https://partner-docs.splashifypro.com/api-reference/rcs-panel/send-message ======================================================================== # Send RCS message Fire a one-off RCS message from your provisioned sender. For bulk sends use [Broadcasts](./broadcasts); this endpoint is for the programmatic side — order shipped, OTP, support reply. ```http POST /api/v1/rcs/messages/send Authorization: Bearer Content-Type: application/json { "to": "+919876543210", "message": { "type": "text", "text": "Hi {{1}}, your order #{{2}} has shipped." }, "extra": "order_shipped_acme_42" } ``` ## Prerequisites - Your account must be in `status = active` and `rcs_active = true` (i.e. an admin has provisioned your RCS sender). If not, the endpoint returns 403 with `code: "rcs_not_active"`. Apply at `/apply-rcs` to start that. - Wallet balance must be `>= rcs_per_msg_price`. The price is debited on a successful gateway accept; rejections refund automatically. ## Request body | Field | Type | Required | Notes | |---|---|---|---| | `to` | string | yes | E.164 (e.g. `+919876543210`) | | `message` | object | yes | Free-form RCS payload — text, rich card, carousel, suggestions, media | | `extra` | string | no | Free-form passthrough metadata; surfaces on the delivery webhook so you can correlate sends | The sender identity is derived from your JWT — you do not pass `bot_name` or any sender override in the body. ## Message shapes ### Plain text ```json { "type": "text", "text": "Your order #12345 has shipped. Track at https://acme.com/o/12345" } ``` ### Text with suggestions ```json { "type": "text", "text": "Anything else?", "suggestions": [ { "type": "reply", "text": "Track order", "postback": "track-12345" }, { "type": "url", "text": "Open site", "url": "https://acme.com" }, { "type": "dial", "text": "Call us", "call_to": "+919876543210" } ] } ``` ### Rich card ```json { "type": "card", "title": "Order #12345 shipped", "description": "Tap to track. ETA Wed.", "media_url": "https://cdn.acme.com/banners/order-shipped.jpg", "suggestions": [ { "type": "url", "text": "Track", "url": "https://acme.com/o/12345" } ] } ``` ### Carousel ```json { "type": "multiple_cards", "cards": [ { "title": "Black tee", "description": "₹499", "media_url": "https://…/tee.jpg", "suggestions": [{"type":"url","text":"Buy","url":"https://acme.com/p/tee"}] }, { "title": "Blue jeans", "description": "₹1299","media_url": "https://…/jeans.jpg", "suggestions": [{"type":"url","text":"Buy","url":"https://acme.com/p/jeans"}] } ] } ``` ## Response `200 OK`: ```json { "success": true, "message_id": "rm_AbCd12…", "cost": 0.95, "sent_at": "2026-05-28T08:00:02Z" } ``` `message_id` is what the gateway returned — keep it if you want to correlate against delivery webhooks. `cost` is the wallet debit. ## Error codes | Status | When | Notes | |---|---|---| | 400 | Missing `to` or `message` | Body validation | | 402 | Wallet balance below `rcs_per_msg_price` | Recharge and retry | | 403 | `rcs_not_active` | Apply for RCS sender first | | 403 | `account suspended` | Contact support | | 502 | Gateway rejected the send | `detail` carries the upstream response | | 503 | Gateway login or transport failure | Safe to retry; the wallet is refunded | ## Idempotency Every POST is treated as a new send. If you retry on a network error you may get duplicate messages. Put your own idempotency key in `extra` and dedupe on the delivery callbacks. ======================================================================== ## Dashboard stats URL: https://partner-docs.splashifypro.com/api-reference/rcs-panel/stats ======================================================================== # Dashboard stats Two endpoints power the RCS Panel dashboard — one returns aggregate KPIs, the other a per-day series for the chart at the top of the page. ```http GET /api/v1/rcs/stats GET /api/v1/rcs/stats/daily?from=YYYY-MM-DD&to=YYYY-MM-DD ``` ## KPIs ```http GET /api/v1/rcs/stats ``` ```json { "success": true, "stats": { "total_broadcasts": 12, "total_recipients": 184320, "total_sent": 175430, "total_delivered": 0, "total_read": 0, "total_failed": 8890, "broadcasts_running": 1, "broadcasts_completed": 9, "broadcasts_cancelled": 1, "broadcasts_failed": 1 }, "wallet": { "balance": 4250.50, "per_message_price": 0.95, "sends_remaining": 4474 }, "bot_name": "acme_promotions" } ``` `total_delivered` and `total_read` stay at zero in v1 — the delivery status webhook routing for RCS-panel customers is a follow-up sprint. ## Daily series ```http GET /api/v1/rcs/stats/daily?from=2026-05-13&to=2026-05-27 ``` `from` and `to` are both optional. Defaults to **the last 14 days**. Max window is **90 days** — anything wider is clamped. ```json { "success": true, "from": "2026-05-13", "to": "2026-05-27", "series": [ { "date": "2026-05-13", "sent": 0, "spend": 0.0 }, { "date": "2026-05-14", "sent": 2105, "spend": 1999.75 }, { "date": "2026-05-15", "sent": 0, "spend": 0.0 }, …, { "date": "2026-05-27", "sent": 18324, "spend": 17407.80 } ] } ``` Every day in the window gets a row — even when you didn't send anything — so the chart line stays continuous. `spend` is the sum of `rcs_wallet_billing_logs.cost` for that day, which is what the wallet was actually debited (not what was invoiced). ======================================================================== ## Templates URL: https://partner-docs.splashifypro.com/api-reference/rcs-panel/templates ======================================================================== # Templates Templates are the message blueprints you'll later attach to a broadcast. Every template goes through an AI compliance review on submission — `approved` lets you broadcast immediately, `needs_admin` sends it to a human reviewer, `rejected` blocks you with a reason. ```http POST /api/v1/rcs/templates GET /api/v1/rcs/templates GET /api/v1/rcs/templates/{id} DELETE /api/v1/rcs/templates/{id} ``` ## Create a template ```http POST /api/v1/rcs/templates Authorization: Bearer Content-Type: application/json { "name": "order_shipped", "category": "transactional", "language": "en_US", "components": { "type": "text", "text": "Your order #{{1}} has shipped. Track at {{2}}." }, "suggestions": [ { "type": "url", "text": "Track order", "url": "{{3}}" } ] } ``` | Field | Type | Required | Notes | |---|---|---|---| | `name` | string | yes | Snake-case, unique per account | | `category` | string | yes | `transactional` \| `promotional` \| `otp` | | `language` | string | yes | BCP-47 (`en_US`, `hi_IN`, …) | | `components` | object | yes | Free-form JSON — text, rich card, carousel, or media | | `suggestions` | array | no | Interactive button definitions | Response: ```json { "success": true, "template_id": "1f7a…", "ai_review_status": "pending", "message": "Template submitted — AI review usually completes within 5s." } ``` Poll `GET /api/v1/rcs/templates/{id}` for the verdict. ## Get one template ```http GET /api/v1/rcs/templates/{id} ``` ```json { "success": true, "template": { "template_id": "1f7a…", "name": "order_shipped", "category": "transactional", "language": "en_US", "components": { "type": "text", "text": "…" }, "suggestions": [ … ], "ai_review_status": "approved", "ai_review_score": 0.93, "ai_review_reason": "Clean transactional template, no policy hits.", "admin_status": "", "admin_reason": "", "effective_status": "approved", "submitted_at": "2026-05-27T08:31:21Z", "reviewed_at": "2026-05-27T08:31:24Z" } } ``` `effective_status` is what broadcasts check — admin override wins when present, otherwise the AI verdict. ## List templates ```http GET /api/v1/rcs/templates ``` Returns `{ "success": true, "templates": [ … ] }` with the same row shape as `GET /…/{id}`, ordered by `submitted_at` desc. ## Delete ```http DELETE /api/v1/rcs/templates/{id} ``` Removes the template. Broadcasts already running with this template keep their assignment and won't break. ======================================================================== ## Delete suppression entry URL: https://partner-docs.splashifypro.com/api-reference/suppression/delete ======================================================================== # Delete suppression entry Removes an email from the suppression list, allowing future sends. ```http DELETE /api/v1/partner/email/suppression/:email ``` > **Caution.** If the address bounced hard or complained, removing > it and re-sending is likely to get it suppressed again + drag > down your reputation. Only remove when you have evidence the > underlying cause is fixed (e.g. user changed providers, mailbox > was reactivated). ## cURL ```bash curl -X DELETE \ https://api.splashifypro.com/api/v1/partner/email/suppression/user@example.com \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ## Response ```json { "success": true } ``` 204 / 404 are both treated as success — if the address wasn't suppressed, the desired end state is already achieved. ======================================================================== ## Get suppression entry URL: https://partner-docs.splashifypro.com/api-reference/suppression/get ======================================================================== # Get suppression entry ```http GET /api/v1/partner/email/suppression/:email ``` Check whether a specific email is on the suppression list. Useful for pre-flight checks before adding an address to a campaign list. ## Response ```json { "success": true, "entry": { "email": "deadbox@example.com", "reason": "BOUNCE", "details": "550 5.1.1 user unknown", "added_at": "2026-05-03T11:30:00Z" } } ``` ## Common errors | Status | Code | Meaning | |---|---|---| | 404 | `NOT_SUPPRESSED` | The email isn't on the suppression list — safe to send | A 404 here is the **normal "not suppressed" response** — branch on status code, not just body content. ## cURL ```bash curl https://api.splashifypro.com/api/v1/partner/email/suppression/deadbox@example.com \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ======================================================================== ## List suppressed addresses URL: https://partner-docs.splashifypro.com/api-reference/suppression/list ======================================================================== # List suppressed addresses ```http GET /api/v1/partner/email/suppression ``` ## Query parameters | Field | Type | Notes | |---|---|---| | `reason` | string | `BOUNCE` / `COMPLAINT` / `UNSUBSCRIBE` / `MANUAL` | | `search` | string | Case-insensitive substring match on email | | `limit` | int | 1-1000, default 200 | ## Response ```json { "success": true, "suppression": [ { "email": "deadbox@example.com", "reason": "BOUNCE", "details": "550 5.1.1 user unknown", "added_at": "2026-05-03T11:30:00Z" }, { "email": "spammed@example.com", "reason": "COMPLAINT", "details": "FBL/abuse", "added_at": "2026-05-03T11:35:00Z" } ], "count": 2 } ``` ## cURL ```bash curl 'https://api.splashifypro.com/api/v1/partner/email/suppression?reason=BOUNCE&limit=50' \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ======================================================================== ## Put suppression entry URL: https://partner-docs.splashifypro.com/api-reference/suppression/put ======================================================================== # Put suppression entry Idempotent — re-PUTting an existing email overwrites the reason + details with the new values. ```http PUT /api/v1/partner/email/suppression/:email ``` ## Request body ```json { "reason": "MANUAL", "details": "GDPR erasure request 2026-05-03 ticket #1024" } ``` | Field | Type | Required | Notes | |---|---|---|---| | `reason` | string | no | `MANUAL` (default) / `BOUNCE` / `COMPLAINT` / `UNSUBSCRIBE` | | `details` | string | no | Free-text note. Surfaced in audit + UI | ## Response ```json { "success": true, "email": "user@example.com", "reason": "MANUAL" } ``` ## Use cases - **Honoring opt-out clicks in your app** — when a user clicks unsubscribe in your preferences page, push them here so you stop sending across all your sending integrations - **GDPR / DPDP erasure** — adds the address with a `MANUAL` reason + a note referencing the ticket - **Pre-emptive blocklist** — known-bad addresses you never want to email, regardless of the source list ## cURL ```bash curl -X PUT \ https://api.splashifypro.com/api/v1/partner/email/suppression/user@example.com \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{"reason": "MANUAL", "details": "User unsubscribed via app"}' ``` ======================================================================== ## Create template URL: https://partner-docs.splashifypro.com/api-reference/templates/create ======================================================================== # Create template Save a reusable template. Reference it on `/send-template` or `/send-bulk` by its `template_name`. ```http POST /api/v1/partner/email/templates ``` ## Request body ```json { "template_name": "welcome", "subject": "Welcome to {{company_name}}, {{first_name}}", "html": "

Hi {{first_name}} 👋

Thanks for joining {{company_name}}.

", "text": "Hi {{first_name}}. Thanks for joining {{company_name}}.", "declared_vars": ["first_name", "company_name"] } ``` | Field | Type | Required | Notes | |---|---|---|---| | `template_name` | string | yes | Unique per partner. Lowercase letters / numbers / `_` `-`, max 64 chars | | `subject` | string | yes | Variables can be used here too | | `html` | string | conditional | One of `html` or `react_email_json` is required | | `text` | string | no | Plaintext fallback. Auto-derived from HTML if omitted | | `react_email_json` | string | conditional | Visual-editor JSON. Renders to `html` + `text` server-side | | `declared_vars` | string[] | no | Variable names the template uses. Surfaced on the panel + helps catch typos | ## Variable syntax Both `{{var_name}}` and `{{ var_name }}` (with spaces) work. Missing variables at send time stay as the literal `{{name}}` in the rendered output rather than raising an error — this keeps hot-path sends forgiving. To enforce required variables, declare them in `declared_vars` and validate yourself before calling `/send-template`. ## Response ```json { "success": true, "template": { "template_id": "tpl_550e8400-...", "template_name": "welcome", "subject": "Welcome to {{company_name}}, {{first_name}}", "html": "

Hi {{first_name}}...", "text": "Hi {{first_name}}...", "declared_vars": ["first_name", "company_name"], "created_at": "...", "updated_at": "..." } } ``` ## cURL ```bash curl https://api.splashifypro.com/api/v1/partner/email/templates \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "template_name": "welcome", "subject": "Hi {{first_name}}", "html": "

Hi {{first_name}}

" }' ``` ## Common errors | Status | Code | Meaning | |---|---|---| | 409 | `TEMPLATE_NAME_TAKEN` | Another template with that name exists | | 400 | `INVALID_REQUEST` | Bad name format or missing both `html` and `react_email_json` | | 400 | `TEMPLATE_LIMIT_REACHED` | 500-template cap per account | ======================================================================== ## Delete template URL: https://partner-docs.splashifypro.com/api-reference/templates/delete ======================================================================== # Delete template ```http DELETE /api/v1/partner/email/templates/:id ``` Removes the template + its name alias. In-flight `/send-template` calls referencing this template by name return `404 TEMPLATE_NOT_FOUND` after the delete commits. ## cURL ```bash curl -X DELETE \ https://api.splashifypro.com/api/v1/partner/email/templates/tpl_550e8400-... \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ## What this does NOT do - Does NOT recall already-queued sends that referenced this template. - Does NOT remove historical send records — `GET /emails/:message_id` still returns the original `template_id` for past sends. ======================================================================== ## Get template URL: https://partner-docs.splashifypro.com/api-reference/templates/get ======================================================================== # Get template ```http GET /api/v1/partner/email/templates/:id ``` Fetch full template content by template_id. ## Response ```json { "success": true, "template": { "template_id": "tpl_550e8400-...", "template_name": "welcome", "subject": "Welcome", "html": "

Hi {{first_name}}

", "text": "Hi {{first_name}}", "react_email_json": "...", "declared_vars": ["first_name"], "created_at": "...", "updated_at": "..." } } ``` ## cURL ```bash curl https://api.splashifypro.com/api/v1/partner/email/templates/tpl_550e8400-... \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ======================================================================== ## Get template by name URL: https://partner-docs.splashifypro.com/api-reference/templates/get-by-name ======================================================================== # Get template by name ```http GET /api/v1/partner/email/templates/by-name/:name ``` Fetch a template by its `template_name`. Same response shape as [GET /templates/:id](/api-reference/templates/get). Useful when your code only knows the friendly name (e.g. from a config file) and not the UUID. ## cURL ```bash curl https://api.splashifypro.com/api/v1/partner/email/templates/by-name/welcome \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ======================================================================== ## List templates URL: https://partner-docs.splashifypro.com/api-reference/templates/list ======================================================================== # List templates ```http GET /api/v1/partner/email/templates ``` ## Response ```json { "success": true, "templates": [ { "template_id": "tpl_550e8400-...", "template_name": "welcome", "subject": "Welcome to {{company_name}}", "declared_vars": ["first_name", "company_name"], "created_at": "2026-05-03T12:00:00Z", "updated_at": "2026-05-03T12:00:00Z" } ], "count": 1 } ``` `html` + `text` + `react_email_json` are NOT included in list responses to keep the payload small. Use [GET /templates/:id](/api-reference/templates/get) or [GET /templates/by-name/:name](/api-reference/templates/get-by-name) to fetch full content. ## cURL ```bash curl https://api.splashifypro.com/api/v1/partner/email/templates \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ======================================================================== ## Preview template URL: https://partner-docs.splashifypro.com/api-reference/templates/preview ======================================================================== # Preview template Render a `react_email_json` payload to HTML + plaintext without saving a template. Useful for live preview UIs in your panel before you persist via `POST /templates`. ```http POST /api/v1/partner/email/templates/preview ``` ## Request body ```json { "react_email_json": "{...}", "variables": { "first_name": "Alex", "company_name": "Acme" } } ``` | Field | Type | Required | Notes | |---|---|---|---| | `react_email_json` | string | yes | Visual-editor block JSON | | `variables` | object | no | Substitution map applied to the rendered output | ## Response ```json { "success": true, "html": "...", "text": "..." } ``` ## cURL ```bash curl https://api.splashifypro.com/api/v1/partner/email/templates/preview \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "react_email_json": "...", "variables": {"first_name": "Alex"} }' ``` ## Common errors | Status | Code | Meaning | |---|---|---| | 503 | `RENDERER_UNAVAILABLE` | Template rendering service is temporarily down — retry | | 502 | `RENDER_FAILED` | The JSON couldn't be compiled. Read the message | ======================================================================== ## Update template URL: https://partner-docs.splashifypro.com/api-reference/templates/update ======================================================================== # Update template ```http PATCH /api/v1/partner/email/templates/:id ``` All fields optional — pass only what's changing. `template_name` cannot be changed; create a new template under the new name and delete the old one. ## Request body ```json { "subject": "Welcome to {{company_name}}", "html": "

Updated welcome 👋

", "text": "Updated welcome.", "declared_vars": ["company_name"] } ``` | Field | Type | Notes | |---|---|---| | `subject` | string | | | `html` | string | | | `text` | string | | | `react_email_json` | string | When supplied, re-renders to fresh `html` + `text` | | `declared_vars` | string[] | Replaces existing list | ## cURL ```bash curl -X PATCH \ https://api.splashifypro.com/api/v1/partner/email/templates/tpl_550e8400-... \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{"subject": "New subject"}' ``` ======================================================================== ## Conversation Analytics URL: https://partner-docs.splashifypro.com/api-reference/whatsapp/analytics-conversations ======================================================================== # Conversation Analytics Counts conversations for a WABA, with optional breakdowns by category, type, direction, country, or phone number. Same WhatsApp Cloud API field passthrough as [Message Analytics](/api-reference/whatsapp/analytics-messages). > **Cost is not returned by this endpoint** — Meta stopped surfacing it > for businesses billed through a partner as of July 1 2023. For > dollar amounts, use your Splashify > [/billing/deductions](/api-reference/customers/send-rcs) ledger. ```http GET /api/v25.0/{WABA_ID}?fields=conversation_analytics.start().end().granularity() Authorization: Bearer pk_live_ ``` ## Customer-scoped alias ```http GET /api/v1/partner/customers/{customer_id}/whatsapp/analytics/conversations?fields=… ``` ## Query parameters Single `fields=` blob: ``` fields=conversation_analytics.start().end().granularity() [.phone_numbers([...])] [.metric_types([CONVERSATION])] [.conversation_categories([AUTHENTICATION,MARKETING,SERVICE,UTILITY])] [.conversation_types([FREE_ENTRY,FREE_TIER,REGULAR])] [.conversation_directions([BUSINESS_INITIATED,USER_INITIATED])] [.dimensions(["CONVERSATION_CATEGORY","CONVERSATION_TYPE","COUNTRY","PHONE"])] ``` | Field | Required | Notes | |---|---|---| | `start` | yes | UNIX timestamp | | `end` | yes | UNIX timestamp | | `granularity` | yes | `HALF_HOUR` / `DAILY` / `MONTHLY` | | `phone_numbers` | no | Limit to specific numbers; omit for all on the WABA | | `metric_types` | no | `CONVERSATION` is the only reliable value for partners | | `conversation_categories` | no | Filter to a subset of `AUTHENTICATION` / `MARKETING` / `SERVICE` / `UTILITY` | | `conversation_types` | no | `FREE_ENTRY` / `FREE_TIER` / `REGULAR` | | `conversation_directions` | no | `BUSINESS_INITIATED` / `USER_INITIATED` | | `dimensions` | no | Break the counts down by `CONVERSATION_CATEGORY`, `CONVERSATION_DIRECTION`, `CONVERSATION_TYPE`, `COUNTRY`, `PHONE` | ## Response — 200 ```json { "conversation_analytics": { "data": [ { "data_points": [ { "start": 1693506600, "end": 1696098600, "conversation": 1, "phone_number": "912240289385", "country": "KW", "conversation_type": "REGULAR", "conversation_category": "MARKETING" } ] } ] }, "id": "115344761664057" } ``` ## Errors Same matrix as [Message Analytics](/api-reference/whatsapp/analytics-messages). ## cURL ```bash curl -X GET \ "https://api.splashifypro.com/api/v25.0/$WABA_ID?fields=conversation_analytics.start(1693506600).end(1706725800).granularity(MONTHLY).dimensions(%5B%22CONVERSATION_CATEGORY%22%2C%22CONVERSATION_TYPE%22%2C%22COUNTRY%22%2C%22PHONE%22%5D)" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ======================================================================== ## Message Analytics URL: https://partner-docs.splashifypro.com/api-reference/whatsapp/analytics-messages ======================================================================== # Message Analytics Returns the number of messages sent and delivered by the phone numbers on a WABA, at the granularity you ask for (half-hour, day, or month). ```http GET /api/v25.0/{WABA_ID}?fields=analytics.start().end().granularity() Authorization: Bearer pk_live_ ``` ## Customer-scoped alias ```http GET /api/v1/partner/customers/{customer_id}/whatsapp/analytics/messages?fields=… ``` ## Query parameters Pass them inline as a single `fields=` blob using the nested-function syntax: ``` fields=analytics.start().end().granularity() [.phone_numbers([...])] [.product_types([0,2])] [.country_codes([...])] ``` | Field | Required | Notes | |---|---|---| | `start` | yes | UNIX timestamp — start of window | | `end` | yes | UNIX timestamp — end of window | | `granularity` | yes | `HALF_HOUR` / `DAY` / `MONTH` | | `phone_numbers` | no | Array of E.164 phone numbers; omit to include all on the WABA | | `product_types` | no | `0` = notification, `2` = customer-support; omit for both | | `country_codes` | no | Array of 2-letter country codes; omit for all | ## Response — 200 ```json { "analytics": { "phone_numbers": ["912240289385"], "granularity": "MONTH", "data_points": [ { "start": 1693506600, "end": 1696098600, "sent": 2497742, "delivered": 2395663 }, { "start": 1696098600, "end": 1698777000, "sent": 4366872, "delivered": 4171084 } ] }, "id": "115344761664057" } ``` ## Errors | Status | Example | |---|---| | `401` | `{ "success": false, "message": "unauthorized" }` | | `403` | `{ "success": false, "message": "id does not belong to your account" }` | | `404` | `{ "success": false, "message": "id not found" }` | | `502` | `{ "success": false, "message": "WhatsApp request failed. Try again shortly." }` | ## cURL ```bash curl -X GET \ "https://api.splashifypro.com/api/v25.0/$WABA_ID?fields=analytics.start(1693506600).end(1706725800).granularity(MONTH)" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ## Notes - The `fields=` query string is the Meta-side nested syntax — we forward it verbatim. - Analytics data is approximate and may differ slightly from invoiced totals due to internal aggregation rounding. ======================================================================== ## Template Analytics URL: https://partner-docs.splashifypro.com/api-reference/whatsapp/analytics-templates ======================================================================== # Template Analytics Two endpoints. **You must enable template insights once** (one-way flag) before the read endpoint returns data. Both are WhatsApp Cloud API analytics-field calls. ## 1. Enable template insights (one-time) ```http POST /api/v25.0/{WABA_ID}?is_enabled_for_insights=true Authorization: Bearer pk_live_ ``` ### Customer-scoped alias ```http POST /api/v1/partner/customers/{customer_id}/whatsapp/analytics/templates/enable ``` Once on, **cannot be turned off** — this is a Meta-side rule, not ours. ### Response — 200 ```json { "id": "102290129340398" } ``` ### cURL ```bash curl -X POST \ "https://api.splashifypro.com/api/v25.0/$WABA_ID?is_enabled_for_insights=true" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ## 2. Get template metrics ```http GET /api/v25.0/{WABA_ID}?fields=template_analytics?start=…&end=…&granularity=DAILY&template_ids=[…]&metric_types=[…] Authorization: Bearer pk_live_ ``` ### Customer-scoped alias ```http GET /api/v1/partner/customers/{customer_id}/whatsapp/analytics/templates?fields=… ``` ### Query parameters Meta's docs nest the parameters after a literal `?` inside the `fields=` value (so the URL has a `?` *twice*). Send the params as the WhatsApp Cloud API specifies — we forward unchanged. | Field | Required | Notes | |---|---|---| | `start` | yes | UNIX timestamp. Times not on a UTC day-boundary are floored to the prior 00:00 UTC | | `end` | yes | UNIX timestamp. Non-boundary times are rounded up to the next 00:00 UTC | | `granularity` | yes | `DAILY` is the only supported value today | | `template_ids` | yes | Array of template IDs. Max 10 per request | | `metric_types` | no | `SENT` / `DELIVERED` / `READ` / `CLICKED`. Omit for all. `CLICKED` only populates for URL / quick-reply buttons on `MARKETING` / `UTILITY` templates | ### Response — 200 ```json { "data": [ { "granularity": "DAILY", "data_points": [ { "template_id": "1924084211297547", "start": 1689379200, "end": 1689465600, "sent": 0, "delivered": 0, "read": 0, "clicked": [ { "type": "quick_reply_button", "button_content": "Tell me more", "count": 3 }, { "type": "quick_reply_button", "button_content": "Get coupon", "count": 5 } ] } ] } ], "paging": { "cursors": { "before": "...", "after": "..." } } } ``` ### Errors | Status | Example | |---|---| | `401` | `{ "success": false, "message": "unauthorized" }` | | `403` | `{ "success": false, "message": "id does not belong to your account" }` | | `404` | `{ "success": false, "message": "id not found" }` | If insights aren't enabled, the response returns an empty `data` array (not a 4xx). Call the enable endpoint first. ### cURL ```bash curl -X GET \ "https://api.splashifypro.com/api/v25.0/$WABA_ID?fields=template_analytics?start=1689379200%26end=1689552000%26granularity=DAILY%26metric_types=[%27SENT%27%2C%27DELIVERED%27%2C%27READ%27%2C%27CLICKED%27]%26template_ids=[1924084211297547%2C954638012257287]" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ======================================================================== ## Block / Unblock Users URL: https://partner-docs.splashifypro.com/api-reference/whatsapp/block-users ======================================================================== # Block / Unblock Users Block a customer from sending you any further messages on a specific phone number (or unblock one you previously blocked). Mirrors the manual block-button in the WhatsApp Business app. ## 1. Block ```http POST /api/v25.0/{PHONE_NUMBER_ID}/block_users Authorization: Bearer pk_live_ Content-Type: application/json ``` ```json { "messaging_product": "whatsapp", "block_users": [ { "user": "+919999999999" }, { "user": "+919888888888" } ] } ``` Max **500** users per request. Submit larger batches in multiple calls. ### Response — 200 ```json { "messaging_product": "whatsapp", "block_users": { "added_users": [ { "input": "+919999999999", "wa_id": "919999999999" }, { "input": "+919888888888", "wa_id": "919888888888" } ], "errors": [] } } ``` If some entries fail (already blocked, invalid number), they appear in `errors` with a Meta-side `code` + `message`. ## 2. Unblock ```http DELETE /api/v25.0/{PHONE_NUMBER_ID}/block_users Authorization: Bearer pk_live_ Content-Type: application/json ``` ```json { "messaging_product": "whatsapp", "block_users": [ { "user": "+919999999999" } ] } ``` ### Response — 200 ```json { "messaging_product": "whatsapp", "block_users": { "removed_users": [ { "input": "+919999999999", "wa_id": "919999999999" } ], "errors": [] } } ``` ## 3. List currently blocked users ```http GET /api/v25.0/{PHONE_NUMBER_ID}/block_users?limit=100 Authorization: Bearer pk_live_ ``` ### Response — 200 ```json { "data": [ { "user": "919999999999", "block_reason": "manual" }, { "user": "919888888888", "block_reason": "manual" } ], "paging": { "cursors": { "before": "...", "after": "..." } } } ``` ## Effects of blocking | Direction | Behaviour | |---|---| | User → Business | Their messages are silently dropped at Meta's edge. They see the message as sent on their side but you never receive a webhook | | Business → User | Outbound `/messages` calls to a blocked user return `(#131048) User is blocked` | | Templates | Marketing templates to blocked users return the same 131048 | | Quality | Blocking does not affect the phone-number quality rating | ## Errors | Body | When | |---|---| | `(#131048) User is blocked` | Outbound send to a blocked user | | `(#100) Cannot block more than 500 users in one request` | Batch too large | | `(#100) Invalid phone number` | `user` isn't in E.164 format | ## cURL ```bash # block curl -X POST \ "https://api.splashifypro.com/api/v25.0/$PHONE_NUMBER_ID/block_users" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "messaging_product": "whatsapp", "block_users": [{ "user": "+919999999999" }] }' # unblock curl -X DELETE \ "https://api.splashifypro.com/api/v25.0/$PHONE_NUMBER_ID/block_users" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "messaging_product": "whatsapp", "block_users": [{ "user": "+919999999999" }] }' ``` ======================================================================== ## Business Profile URL: https://partner-docs.splashifypro.com/api-reference/whatsapp/business-profile ======================================================================== # Business Profile Read and update the business profile shown on the WhatsApp customer side of one phone number — about-text, description, vertical, websites, email, address, profile picture. ## GET — read ```http GET /api/v25.0/{PHONE_NUMBER_ID}/whatsapp_business_profile?fields=about,address,description,email,profile_picture_url,websites,vertical Authorization: Bearer pk_live_ ``` ### Response — 200 ```json { "data": [ { "about": "Splashify — partner-grade messaging.", "address": "Mumbai, IN", "description": "WhatsApp + RCS + Email partner platform.", "email": "hello@example.com", "profile_picture_url": "https://…", "websites": [ "https://example.com" ], "vertical": "PROFESSIONAL_SERVICES", "messaging_product": "whatsapp" } ] } ``` ## POST — update ```http POST /api/v25.0/{PHONE_NUMBER_ID}/whatsapp_business_profile Authorization: Bearer pk_live_ Content-Type: application/json ``` ### Request body Send only the fields you want to change — omit the rest. ```json { "messaging_product": "whatsapp", "about": "Splashify — partner-grade messaging.", "address": "Mumbai, IN", "description": "WhatsApp + RCS + Email partner platform.", "email": "hello@example.com", "websites": ["https://example.com"], "vertical": "PROFESSIONAL_SERVICES", "profile_picture_handle": "" } ``` `vertical` must be one of: `AUTO`, `BEAUTY`, `APPAREL`, `EDU`, `ENTERTAIN`, `EVENT_PLAN`, `FINANCE`, `GROCERY`, `GOVT`, `HOTEL`, `HEALTH`, `NONPROFIT`, `PROF_SERVICES`, `RETAIL`, `TRAVEL`, `RESTAURANT`, `OTHER`. `profile_picture_handle` is the handle returned by uploading a picture through the [Media](/api-reference/whatsapp/media) endpoint. ### Response — 200 ```json { "success": true } ``` ## Errors Same matrix as [Phone Number Details](/api-reference/whatsapp/phone-number-details). ## cURL — update ```bash curl -X POST \ "https://api.splashifypro.com/api/v25.0/$PHONE_NUMBER_ID/whatsapp_business_profile" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "messaging_product": "whatsapp", "about": "Splashify Pro", "vertical": "PROFESSIONAL_SERVICES" }' ``` ======================================================================== ## Commerce URL: https://partner-docs.splashifypro.com/api-reference/whatsapp/commerce ======================================================================== # Commerce Two-part commerce surface: 1. **Settings** — per-phone-number flags that turn the cart on/off and show/hide the catalog icon. 2. **Product messages** — send `interactive` messages of type `product` / `product_list` that render an in-app product card. The catalog itself lives in Meta Commerce Manager — link it to your WABA in Business Manager first; we only toggle visibility here. ## 1. Read commerce settings ```http GET /api/v25.0/{PHONE_NUMBER_ID}/whatsapp_commerce_settings Authorization: Bearer pk_live_ ``` ### Response — 200 ```json { "data": [ { "is_cart_enabled": true, "is_catalog_visible": true } ] } ``` ## 2. Enable / Disable Cart ```http POST /api/v25.0/{PHONE_NUMBER_ID}/whatsapp_commerce_settings?is_cart_enabled=true Authorization: Bearer pk_live_ ``` Toggle flag goes in the **query string**, no body required. | Param | Notes | |---|---| | `is_cart_enabled` | `true` to enable cart, `false` to disable | ### Response — 200 ```json { "success": true } ``` ## 3. Enable / Disable Catalog ```http POST /api/v25.0/{PHONE_NUMBER_ID}/whatsapp_commerce_settings?is_catalog_visible=true Authorization: Bearer pk_live_ ``` | Param | Notes | |---|---| | `is_catalog_visible` | `true` to show the catalog icon in the chat header, `false` to hide it | ## 4. Send a single product message Use [Send Messages](/api-reference/whatsapp/messages) with `type: "interactive"` and `interactive.type: "product"`: ```json { "messaging_product": "whatsapp", "to": "+919999999999", "type": "interactive", "interactive": { "type": "product", "body": { "text": "Featured product" }, "footer": { "text": "Free shipping on orders over ₹999" }, "action": { "catalog_id": "1234567890123456", "product_retailer_id": "SKU-ABC-123" } } } ``` `product_retailer_id` is the SKU you set in Commerce Manager — not the Meta-internal product ID. ## 5. Send a product list ```json { "messaging_product": "whatsapp", "to": "+919999999999", "type": "interactive", "interactive": { "type": "product_list", "header": { "type": "text", "text": "Diwali picks" }, "body": { "text": "Hand-picked sweets for the week" }, "footer": { "text": "Tap any item to view" }, "action": { "catalog_id": "1234567890123456", "sections": [ { "title": "Sweets", "product_items": [ { "product_retailer_id": "SKU-001" }, { "product_retailer_id": "SKU-002" } ] }, { "title": "Savouries", "product_items": [ { "product_retailer_id": "SKU-101" }, { "product_retailer_id": "SKU-102" } ] } ] } } } ``` Max 10 sections, 30 products per list. ## 6. Send a catalog message ```json { "messaging_product": "whatsapp", "to": "+919999999999", "type": "interactive", "interactive": { "type": "catalog_message", "body": { "text": "Browse our full catalog" }, "action": { "name": "catalog_message", "parameters": { "thumbnail_product_retailer_id": "SKU-001" } } } } ``` ## 7. Receive orders When a customer submits a cart, Meta delivers a webhook with `type: "order"` to your configured webhook URL: ```json { "type": "order", "order": { "catalog_id": "1234567890123456", "text": "Add a note: please gift wrap", "product_items": [ { "product_retailer_id": "SKU-001", "quantity": 2, "item_price": 499.0, "currency": "INR" } ] } } ``` Acknowledge and process server-side — Meta does not handle payment or fulfilment. ## Errors Same matrix as [Phone Number Details](/api-reference/whatsapp/phone-number-details). Product-message-specific errors: | Body | When | |---|---| | `(#131009) Parameter value is not valid` — `product_retailer_id` not in catalog | SKU isn't in the linked catalog, or catalog isn't approved | | `(#100) Catalog is not visible` | `is_catalog_visible` is false; flip it on first | ## cURL — toggle + send ```bash # Enable cart curl -X POST \ "https://api.splashifypro.com/api/v25.0/$PHONE_NUMBER_ID/whatsapp_commerce_settings?is_cart_enabled=true" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" # Show catalog icon curl -X POST \ "https://api.splashifypro.com/api/v25.0/$PHONE_NUMBER_ID/whatsapp_commerce_settings?is_catalog_visible=true" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" # Send product curl -X POST \ "https://api.splashifypro.com/api/v25.0/$PHONE_NUMBER_ID/messages" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "messaging_product": "whatsapp", "to": "+919999999999", "type": "interactive", "interactive": { "type": "product", "body": { "text": "Featured product" }, "action": { "catalog_id": "1234567890123456", "product_retailer_id": "SKU-ABC-123" } } }' ``` ======================================================================== ## Conversational Components URL: https://partner-docs.splashifypro.com/api-reference/whatsapp/conversational-components ======================================================================== # Conversational Components Three in-app affordances that improve first-message quality: | Component | What the user sees | |---|---| | **Welcome Message** | An auto-sent greeting the first time a customer opens the chat with your number | | **Ice Breakers** | Up to 4 tappable prompts shown above the input field on an empty chat | | **Commands** | Slash-commands (`/help`, `/track`, …) that appear when the user types `/` | All three are configured per-phone-number via a single endpoint. ## 1. Read current configuration ```http GET /api/v25.0/{PHONE_NUMBER_ID}?fields=conversational_automation Authorization: Bearer pk_live_ ``` ### Response — 200 ```json { "conversational_automation": { "enable_welcome_message": true, "prompts": [ "How do I track my order?", "What's your return policy?", "Talk to a human" ], "commands": [ { "command_name": "help", "command_description": "Show available commands" }, { "command_name": "track", "command_description": "Track your latest order" } ] }, "id": "112269058640637" } ``` ## 2. Update configuration ```http POST /api/v25.0/{PHONE_NUMBER_ID}/conversational_automation Authorization: Bearer pk_live_ Content-Type: application/json ``` ### Request body Send only the fields you want to change. Omitted fields are left as-is; sending an empty array (`[]`) clears that component. ```json { "enable_welcome_message": true, "prompts": [ "How do I track my order?", "What's your return policy?", "Talk to a human" ], "commands": [ { "command_name": "help", "command_description": "Show available commands" }, { "command_name": "track", "command_description": "Track your latest order" }, { "command_name": "human", "command_description": "Connect to a human agent" } ] } ``` ### Field rules | Field | Constraints | |---|---| | `enable_welcome_message` | Boolean. When `true`, Meta sends a `request_welcome` webhook to your endpoint on first contact — you reply with the actual welcome text | | `prompts` | 1–4 entries, each ≤ 80 chars. Empty array clears prompts | | `commands` | 1–30 entries. `command_name` is 1–32 lowercase alphanumeric + `_`; `command_description` ≤ 256 chars | ### Response — 200 ```json { "success": true } ``` ## Welcome Message — how it actually fires Setting `enable_welcome_message: true` does **not** define the message text. Instead, when a customer opens the chat for the first time, Meta calls your webhook with: ```json { "type": "request_welcome", "from": "+919999999999" } ``` You then call [Send Messages](/api-reference/whatsapp/messages) within **60 seconds** with whatever welcome you want — text, template, media, interactive, or even a flow. Miss the window and the user sees nothing. ## Ice Breakers — UX rules - Always shown on **empty chats only**. Once the customer has sent one message, the prompts disappear forever for that conversation. - Tapping a prompt sends it as a normal text message — you receive it in your webhook as `type: "text"`, no special marker. To attribute, match the body against your configured prompts. ## Commands — UX rules - The user types `/` and a picker appears. Tapping a command sends it as a text message — `/help`, `/track`, etc. - You receive it as a normal text webhook. Route by exact match on the leading `/`. - Updating commands takes effect on Meta's side within a minute, but the client may cache the old list for up to an hour. ## Errors | Body | When | |---|---| | `(#100) Invalid parameter — prompts` | One of the prompts exceeds 80 chars | | `(#100) Invalid parameter — commands` | `command_name` contains uppercase, dashes, or spaces | | `(#136025) Conversational automation rate limited` | More than ~5 updates in 5 minutes — back off | ## cURL — set ice breakers + commands ```bash curl -X POST \ "https://api.splashifypro.com/api/v25.0/$PHONE_NUMBER_ID/conversational_automation" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "enable_welcome_message": true, "prompts": [ "Track my order", "Return policy", "Talk to a human" ], "commands": [ { "command_name": "help", "command_description": "Show available commands" }, { "command_name": "track", "command_description": "Track your latest order" } ] }' ``` ## Notes - **Per-phone-number, not per-WABA.** If you operate multiple numbers for one brand, set the same prompts on each — Meta does not share configuration across numbers. - **Localisation.** Meta picks a single language per phone number; there's no per-recipient translation today. Use the phone number's `verified_name` language to decide. ======================================================================== ## Click-to-WhatsApp Ads (CTWA) URL: https://partner-docs.splashifypro.com/api-reference/whatsapp/ctwa ======================================================================== # Click-to-WhatsApp Ads (CTWA) Create Meta ads (Facebook, Instagram, Messenger) that, when tapped, open a WhatsApp chat with your business number. The ad lives on Meta's Marketing API surface, so the first path segment here is your Meta ad account ID prefixed with `act_` (not a WABA or phone number). The flow is five POSTs in order: ``` 1. Create Campaign → returns CAMPAIGN_ID 2. Create Ad Set → references CAMPAIGN_ID, returns ADSET_ID 3. Create Ad Creative → returns CREATIVE_ID 4. Create Ad → references ADSET_ID + CREATIVE_ID, returns AD_ID 5. Publish Ad → flips AD_ID's status to ACTIVE ``` Every step has a matching `GET` for read-back. ## 1. Create Ad Campaign ```http POST /api/v25.0/act_{AD_ACCOUNT_ID}/campaigns Authorization: Bearer pk_live_ Content-Type: application/json ``` ```json { "name": "Diwali Sale 2026", "objective": "OUTCOME_ENGAGEMENT", "status": "PAUSED", "special_ad_categories": [] } ``` | Field | Notes | |---|---| | `objective` | `OUTCOME_ENGAGEMENT` is the standard CTWA objective. Other accepted values: `OUTCOME_LEADS`, `OUTCOME_SALES` | | `status` | Create as `PAUSED`, publish later in step 5 | | `special_ad_categories` | Empty array unless this ad falls under credit / employment / housing / political categories | ### Response — 200 ```json { "id": "23857890123456789" } ``` ### Read back ```http GET /api/v25.0/{CAMPAIGN_ID}?fields=id,name,objective,status,effective_status ``` ## 2. Create Ad Set ```http POST /api/v25.0/act_{AD_ACCOUNT_ID}/adsets Authorization: Bearer pk_live_ Content-Type: application/json ``` ```json { "name": "Diwali Sale 2026 — India 25-34", "campaign_id": "23857890123456789", "daily_budget": 100000, "billing_event": "IMPRESSIONS", "optimization_goal": "CONVERSATIONS", "promoted_object": { "page_id": "{PAGE_ID}", "whatsapp_phone_number": "+919876543210" }, "targeting": { "geo_locations": { "countries": ["IN"] }, "age_min": 25, "age_max": 34 }, "status": "PAUSED", "start_time": "2026-10-01T00:00:00+0530" } ``` | Field | Notes | |---|---| | `daily_budget` | Lowest currency unit (paise / cents). 100000 = ₹1,000/day | | `optimization_goal` | `CONVERSATIONS` for CTWA | | `promoted_object.whatsapp_phone_number` | The number the ad routes to. Must be a registered WhatsApp Business number on your account | | `targeting` | Meta Marketing API targeting spec — geo, age, interests, custom audiences | ### Response — 200 ```json { "id": "23857890234567890" } ``` ### Read back ```http GET /api/v25.0/{ADSET_ID}?fields=id,name,campaign_id,daily_budget,status,effective_status,optimization_goal,promoted_object,targeting ``` ## 3. Create Ad Creative ```http POST /api/v25.0/act_{AD_ACCOUNT_ID}/adcreatives Authorization: Bearer pk_live_ Content-Type: application/json ``` ```json { "name": "Diwali Sale 2026 — Creative v1", "object_story_spec": { "page_id": "{PAGE_ID}", "link_data": { "message": "Diwali offer — 30% off, tap to chat!", "link": "https://wa.me/919876543210?text=I%27d%20like%20to%20know%20more", "image_hash":"", "call_to_action": { "type": "WHATSAPP_MESSAGE", "value": { "app_destination": "WHATSAPP" } } } } } ``` The `link` is a `wa.me` URL — Meta optimises it as a CTWA destination. `image_hash` comes from uploading an image to `/act_{AD_ACCOUNT_ID}/adimages` (one extra step not strictly part of the 5-step flow). ### Response — 200 ```json { "id": "23857890345678901" } ``` ### Read back ```http GET /api/v25.0/{CREATIVE_ID}?fields=id,name,object_story_spec,image_hash ``` ## 4. Create Ad ```http POST /api/v25.0/act_{AD_ACCOUNT_ID}/ads Authorization: Bearer pk_live_ Content-Type: application/json ``` ```json { "name": "Diwali Sale 2026 — Ad 1", "adset_id": "23857890234567890", "creative": { "creative_id": "23857890345678901" }, "status": "PAUSED" } ``` ### Response — 200 ```json { "id": "23857890456789012" } ``` ### Read back ```http GET /api/v25.0/{AD_ID}?fields=id,name,adset_id,creative,status,effective_status ``` ## 5. Publish Ad (flip to ACTIVE) ```http POST /api/v25.0/{AD_ID} Authorization: Bearer pk_live_ Content-Type: application/json ``` ```json { "status": "ACTIVE" } ``` ### Response — 200 ```json { "success": true } ``` Once `effective_status` flips to `ACTIVE` (a few seconds after this call), the ad starts serving. Inbound messages from users who tap through include a `referral` block in your webhook — see [Send Messages — webhook fan-out](/api-reference/whatsapp/messages) for the payload shape. ## Errors | Status | Body | When | |---|---|---| | `400` | `(#100) Invalid parameter` | Missing required field at any step; check the Meta Marketing API field reference for the exact constraint | | `400` | `(#200) Permissions error` | Your ad account lacks `ads_management` permission, or the WhatsApp number isn't linked to the Page | | `400` | `(#1487749) Insufficient funds` | Ad account has no active payment method or balance | | `403` | `id does not belong to your account` | The `act_` isn't linked to a customer under your partner account | ## cURL — full 5-step flow ```bash # 1) Campaign CAMP_ID=$(curl -s -X POST \ "https://api.splashifypro.com/api/v25.0/act_$AD_ACCOUNT_ID/campaigns" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Diwali 2026", "objective": "OUTCOME_ENGAGEMENT", "status": "PAUSED", "special_ad_categories": [] }' | jq -r .id) # 2) Ad Set ADSET_ID=$(curl -s -X POST \ "https://api.splashifypro.com/api/v25.0/act_$AD_ACCOUNT_ID/adsets" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d "{ \"name\":\"India 25-34\", \"campaign_id\":\"$CAMP_ID\", \"daily_budget\":100000, \"billing_event\":\"IMPRESSIONS\", \"optimization_goal\":\"CONVERSATIONS\", \"promoted_object\":{\"page_id\":\"$PAGE_ID\",\"whatsapp_phone_number\":\"+919876543210\"}, \"targeting\":{\"geo_locations\":{\"countries\":[\"IN\"]},\"age_min\":25,\"age_max\":34}, \"status\":\"PAUSED\", \"start_time\":\"2026-10-01T00:00:00+0530\" }" | jq -r .id) # 3) Creative — assumes you've already uploaded the image to /act_$AD_ACCOUNT_ID/adimages CREATIVE_ID=$(curl -s -X POST \ "https://api.splashifypro.com/api/v25.0/act_$AD_ACCOUNT_ID/adcreatives" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d "{ \"name\":\"Diwali Creative v1\", \"object_story_spec\":{ \"page_id\":\"$PAGE_ID\", \"link_data\":{ \"message\":\"Diwali offer — 30% off!\", \"link\":\"https://wa.me/919876543210\", \"image_hash\":\"$IMAGE_HASH\", \"call_to_action\":{\"type\":\"WHATSAPP_MESSAGE\",\"value\":{\"app_destination\":\"WHATSAPP\"}} } } }" | jq -r .id) # 4) Ad AD_ID=$(curl -s -X POST \ "https://api.splashifypro.com/api/v25.0/act_$AD_ACCOUNT_ID/ads" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d "{ \"name\":\"Diwali Ad 1\", \"adset_id\":\"$ADSET_ID\", \"creative\":{\"creative_id\":\"$CREATIVE_ID\"}, \"status\":\"PAUSED\" }" | jq -r .id) # 5) Publish curl -X POST \ "https://api.splashifypro.com/api/v25.0/$AD_ID" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "status": "ACTIVE" }' ``` ## Notes - **Ad account must be linked** to a customer under your partner account before these endpoints work — until that link exists, the `act_` resolves to 403. Contact support to onboard an ad account. - **CTWA attribution** comes through on inbound message webhooks as a `referral` block with `source_type: "ad"`, `source_id`, and `ctwa_clid`. Persist `ctwa_clid` on first contact — it's the join key for downstream conversion attribution. - **Targeting + budget** follow Meta Marketing API semantics — detailed targeting docs live with Meta, not here. ======================================================================== ## Update Display Name URL: https://partner-docs.splashifypro.com/api-reference/whatsapp/display-name ======================================================================== # Update Display Name The verified business name (shown at the top of every customer chat) is updated through the same endpoint used to register a number. Send the new name in the body; Meta puts it through name review and flips your number to `PENDING_REVIEW` until approved. ```http POST /api/v25.0/{PHONE_NUMBER_ID}/register Authorization: Bearer pk_live_ Content-Type: application/json ``` ## Request body ```json { "messaging_product": "whatsapp", "pin": "123456", "new_verified_name": "Splashify Pro" } ``` | Field | Notes | |---|---| | `pin` | The 6-digit two-step-verification PIN registered on this number | | `new_verified_name` | Proposed display name. 1–25 characters. Must match the legal/trade name on the linked Business Verification | ## Response — 200 ```json { "success": true } ``` The request enters Meta's review queue. Check status via [Phone Number Details](/api-reference/whatsapp/phone-number-details): ```bash GET /api/v25.0/{PHONE_NUMBER_ID}?fields=name_status,verified_name ``` | `name_status` | Meaning | |---|---| | `APPROVED` | Live — customers see the new name | | `PENDING_REVIEW` | Meta is reviewing; old name is still shown | | `DECLINED` | Rejected. The previous name stays. Reasons are NOT exposed via API — check WhatsApp Manager | | `EXPIRED` | Submission timed out (rare; resubmit) | ## Naming rules Meta rejects names that: - Contain emoji, special characters (other than `&`, `'`, `.`, `-`), or excessive capitalisation - Use generic terms ("Customer Care", "Helpdesk", "Sales") without the brand - Imply affiliation with platforms you don't own ("WhatsApp Support", "Meta Verified") - Reference a different business than the one on file in Business Verification ## Errors | Body | When | |---|---| | `(#100) new_verified_name too short` / `too long` | Outside 1–25 chars | | `(#100) Invalid character in new_verified_name` | Special chars not in the allow-list | | `(#136025) Two-step verification pin mismatch` | Wrong `pin` | | `(#131045) Phone number not verified` | The number is `EXPIRED` or `NOT_VERIFIED` — re-run [Verification Code](/api-reference/whatsapp/verification-code) first | | `(#136100) Name change rate-limited` | You can submit a name change once per 30 days | ## cURL ```bash curl -X POST \ "https://api.splashifypro.com/api/v25.0/$PHONE_NUMBER_ID/register" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "messaging_product": "whatsapp", "pin": "123456", "new_verified_name": "Splashify Pro" }' ``` ## Notes - **One endpoint, two roles.** `/register` is also how you bring a number online after onboarding — see [Register / Deregister](/api-reference/whatsapp/registration). To change ONLY the display name, send `new_verified_name` with the PIN. To register a number for the first time, send just the PIN. - **Approved names are sticky.** Once `APPROVED`, the new name appears immediately in the WhatsApp client (caching aside). The old name is not retrievable. ======================================================================== ## WhatsApp Flows URL: https://partner-docs.splashifypro.com/api-reference/whatsapp/flows ======================================================================== # WhatsApp Flows WhatsApp Flows are multi-screen interactive forms Meta hosts on your behalf — appointment booking, lead capture, surveys, order confirmation. You define the screens as JSON, attach the flow to a `MARKETING` / `UTILITY` template (or send it directly as an interactive message), and Meta renders it in the WhatsApp client. ## 1. Create a flow ```http POST /api/v25.0/{WABA_ID}/flows Authorization: Bearer pk_live_ Content-Type: application/json ``` ### Request body ```json { "name": "lead_capture_v1", "categories": ["LEAD_GENERATION"] } ``` | Field | Notes | |---|---| | `name` | Internal name, ≤ 200 chars, unique per WABA | | `categories` | Array. One or more of `SIGN_UP`, `SIGN_IN`, `APPOINTMENT_BOOKING`, `LEAD_GENERATION`, `CONTACT_US`, `CUSTOMER_SUPPORT`, `SURVEY`, `OTHER` | ### Response — 200 ```json { "id": "1234567890123456" } ``` The returned `id` is the **flow-id** used by every other endpoint below. ## 2. List flows ```http GET /api/v25.0/{WABA_ID}/flows Authorization: Bearer pk_live_ ``` ### Response — 200 ```json { "data": [ { "id": "1234567890123456", "name": "lead_capture_v1", "status": "DRAFT", "categories": ["LEAD_GENERATION"], "validation_errors": [] } ], "paging": { "cursors": { "before": "...", "after": "..." } } } ``` `status` is one of `DRAFT`, `PUBLISHED`, `DEPRECATED`, `BLOCKED`, `THROTTLED`. ## 3. Get one flow ```http GET /api/v25.0/{FLOW_ID}?fields=id,name,status,categories,validation_errors,preview Authorization: Bearer pk_live_ ``` The `preview` field returns a temporary preview URL you can open in a browser to walk through the flow without sending it. ## 4. Update flow metadata ```http POST /api/v25.0/{FLOW_ID} Authorization: Bearer pk_live_ Content-Type: application/json ``` ```json { "name": "lead_capture_v2", "categories": ["LEAD_GENERATION", "CONTACT_US"] } ``` ## 5. Upload flow JSON (the screens) ```http POST /api/v25.0/{FLOW_ID}/assets Authorization: Bearer pk_live_ Content-Type: multipart/form-data ``` ### Form fields | Field | Notes | |---|---| | `name` | `flow.json` | | `asset_type` | `FLOW_JSON` | | `file` | The flow definition. See Meta's Flow JSON schema for shape | ### Response — 200 ```json { "success": true, "validation_errors": [] } ``` If `validation_errors` is non-empty, the JSON has schema problems (unknown component, missing route, invalid endpoint binding) and the flow stays in `DRAFT` with the errors attached. ## 6. Publish ```http POST /api/v25.0/{FLOW_ID}/publish Authorization: Bearer pk_live_ ``` ### Response — 200 ```json { "success": true } ``` Once published, you can send the flow via [Send Messages](/api-reference/whatsapp/messages) using `type: "interactive"` + `interactive.type: "flow"`: ```json { "messaging_product": "whatsapp", "to": "+919999999999", "type": "interactive", "interactive": { "type": "flow", "body": { "text": "Tap to book" }, "action": { "name": "flow", "parameters": { "flow_message_version": "3", "flow_id": "1234567890123456", "flow_cta": "Book now", "flow_action": "navigate", "flow_action_payload": { "screen": "WELCOME_SCREEN", "data": { "user_name": "Aditya" } } } } } } ``` ## 7. Deprecate / delete ```http POST /api/v25.0/{FLOW_ID}/deprecate DELETE /api/v25.0/{FLOW_ID} Authorization: Bearer pk_live_ ``` Deprecating keeps the flow visible to existing recipients but blocks new sends. Delete removes it entirely; only `DRAFT` flows can be deleted. ## Webhook responses When a user completes a flow, the response screen-data lands at your configured webhook URL as a normal message webhook with `type: "interactive"` and `interactive.type: "nfm_reply"`. The `response_json` field is the user's submitted data. ## Errors | Status | Example | |---|---| | `400` | Meta returns 400 for malformed flow JSON, unsupported component, etc. | | `401` | `{ "success": false, "message": "unauthorized" }` | | `403` | `{ "success": false, "message": "id does not belong to your account" }` | | `404` | `{ "success": false, "message": "id not found" }` | ## cURL — full lifecycle ```bash # 1) Create FLOW_ID=$(curl -X POST \ "https://api.splashifypro.com/api/v25.0/$WABA_ID/flows" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "lead_capture_v1", "categories": ["LEAD_GENERATION"] }' \ | jq -r .id) # 2) Upload JSON curl -X POST \ "https://api.splashifypro.com/api/v25.0/$FLOW_ID/assets" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -F "name=flow.json" \ -F "asset_type=FLOW_JSON" \ -F "file=@/path/to/flow.json" # 3) Publish curl -X POST \ "https://api.splashifypro.com/api/v25.0/$FLOW_ID/publish" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ## Notes - **Flow versioning is implicit** — uploading new `flow.json` to an existing flow replaces the screens. To roll back, keep your old JSON in source control and re-upload. - **Endpoint-bound flows** require your own HTTPS endpoint that responds to navigation events. Configure the endpoint URL in `flow.json`'s `routing_model`. Meta's request signing uses your business public key; rotate that key via `POST /api/v25.0/{PHONE_NUMBER_ID}/whatsapp_business_encryption`. - **Preview URLs expire** after ~10 minutes — fetch a fresh one each time you need to inspect. ======================================================================== ## Groups URL: https://partner-docs.splashifypro.com/api-reference/whatsapp/groups ======================================================================== # Groups Create a WhatsApp group seeded with members of your choice. The business number becomes the admin; members are added as part of the create call. ```http POST /api/v25.0/{PHONE_NUMBER_ID}/groups Authorization: Bearer pk_live_ Content-Type: application/json ``` ## Request body ```json { "messaging_product": "whatsapp", "subject": "Diwali VIP Customers", "members": [ "+919999999999", "+919888888888", "+919777777777" ] } ``` | Field | Notes | |---|---| | `subject` | Group display name, ≤ 100 characters | | `members` | Array of E.164 phone numbers to add. Each must have an active WhatsApp account. Max 256 members per group | ## Response — 200 ```json { "group_id": "120363012345678901@g.us", "invite_url": "https://chat.whatsapp.com/ABC123XYZ", "added_members": [ { "input": "+919999999999", "wa_id": "919999999999" } ], "errors": [ { "input": "+919777777777", "code": 131009, "message": "Recipient phone number does not have a WhatsApp account" } ] } ``` `group_id` is opaque — store it alongside the customer's account if you need to reference the group later (for sending messages, adding more members, etc.). `invite_url` is a permanent join link until revoked from the WhatsApp client. ## Send a message into a group Use [Send Messages](/api-reference/whatsapp/messages) with the `group_id` as `to`: ```json { "messaging_product": "whatsapp", "to": "120363012345678901@g.us", "type": "text", "text": { "body": "Welcome to the VIP group!" } } ``` ## Errors | Body | When | |---|---| | `(#100) subject is too long` | Trim to ≤ 100 chars | | `(#100) Maximum 256 members per group` | Split into multiple groups | | `(#131009) Recipient phone number does not have a WhatsApp account` | Per-member error — surfaces in the `errors` array, not as a top-level failure | | `(#100) Group feature not enabled for this number` | Group sends are a gated feature; contact support | ## cURL ```bash curl -X POST \ "https://api.splashifypro.com/api/v25.0/$PHONE_NUMBER_ID/groups" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "messaging_product": "whatsapp", "subject": "Diwali VIP", "members": [ "+919999999999", "+919888888888" ] }' ``` ## Notes - **Group billing.** Each outbound message into a group counts as one message per member, billed at the per-category rate. - **Group sends are template-only.** Free-form text into a group is only allowed within the 24-hour customer service window for at least one of the members; outside that, send a `UTILITY` / `MARKETING` template. - **No leave / kick / add API today.** Member management after creation happens through the WhatsApp Business app, not the API. ======================================================================== ## Health Status URL: https://partner-docs.splashifypro.com/api-reference/whatsapp/health-status ======================================================================== # Health Status Pre-flight readiness check before a big send. Returns a structured list of every component (WABA, each phone number, each message template) with `OK` / `LIMITED` / `BLOCKED` state and the reason when not OK. ```http GET /api/v25.0/{WABA_ID}?fields=health_status Authorization: Bearer pk_live_ ``` ## Response — 200 ```json { "health_status": { "can_send_message": "AVAILABLE", "entities": [ { "entity_type": "BUSINESS", "id": "1144556677889900", "can_send_message": "AVAILABLE" }, { "entity_type": "WABA", "id": "115344761664057", "can_send_message": "AVAILABLE" }, { "entity_type": "PHONE_NUMBER", "id": "112269058640637", "can_send_message": "LIMITED", "errors": [ { "error_code": 130472, "error_description": "Quality rating is LOW", "possible_solution": "Improve message quality. Reduce marketing volume until quality returns to GREEN." } ] }, { "entity_type": "MESSAGE_TEMPLATE", "id": "964823735155832", "can_send_message": "BLOCKED", "errors": [ { "error_code": 135000, "error_description": "Template paused due to low quality", "possible_solution": "Edit the template and resubmit for review." } ] } ] }, "id": "115344761664057" } ``` ### `can_send_message` values | Value | Meaning | |---|---| | `AVAILABLE` | Component is healthy — sends will go through | | `LIMITED` | Component is partially throttled — sends work but may be slow or capped | | `BLOCKED` | Component cannot send — surface the `errors` list to your support team | ### Top-level vs entity-level The top-level `can_send_message` is the **worst** state across all entities. If any phone number or template is `BLOCKED`, the top-level goes `BLOCKED` even if everything else is fine. ## Common error codes | `error_code` | What it means | |---|---| | `130472` | Phone-number quality rating is LOW or below | | `131045` | Phone-number not registered (call [Register](/api-reference/whatsapp/registration)) | | `133010` | WABA suspended due to policy violation | | `135000` | Template paused for low quality — re-edit + re-submit | | `135003` | Template rejected at re-submission — review template guidelines | | `131008` | Required parameter missing in template body | | `131056` | Per-recipient marketing cap hit | ## Use cases - **Before a big broadcast** — check health on the WABA. If any phone number is `LIMITED`, route the send through a healthy one. - **Template scheduling** — run health every 30 minutes for templates you depend on. Pause your scheduler when a template flips to `BLOCKED`. - **Quality dashboards** — surface this directly in your customer's panel; the `possible_solution` field is end-user-friendly. ## cURL ```bash curl -X GET \ "https://api.splashifypro.com/api/v25.0/$WABA_ID?fields=health_status" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ## Notes - **Eventually consistent.** Health updates ~15 minutes after a state change — don't expect real-time signals here. - **Cheap to call.** Health is a read-side index Meta keeps warm; polling every 30s costs no quota. - **Pair with webhooks.** Meta also sends `messaging_handovers` and `account_alerts` webhooks for critical state changes — health is the snapshot, webhooks are the deltas. ======================================================================== ## Mark Message as Read URL: https://partner-docs.splashifypro.com/api-reference/whatsapp/mark-as-read ======================================================================== # Mark Message as Read Send a read receipt for an inbound message — the customer sees the blue ticks. Same endpoint as [Send Messages](/api-reference/whatsapp/messages), different envelope. ```http POST /api/v25.0/{PHONE_NUMBER_ID}/messages Authorization: Bearer pk_live_ Content-Type: application/json ``` ## Request body — mark as read ```json { "messaging_product": "whatsapp", "status": "read", "message_id": "wamid.HBgMOTE5OTk5OTk5OTk5FQIAERgSNzg5MTIzNDU2Nzg5MEFCQ0RFAA==" } ``` | Field | Notes | |---|---| | `status` | Always `read` for this envelope | | `message_id` | The `wamid` from the inbound webhook you're acknowledging | ### Response — 200 ```json { "success": true } ``` ## Request body — with typing indicator While the read receipt is pending, you can also show a "..." typing indicator. Same envelope plus `typing_indicator`: ```json { "messaging_product": "whatsapp", "status": "read", "message_id": "wamid.…", "typing_indicator": { "type": "text" } } ``` The indicator auto-dismisses after ~25 seconds or when you send a real reply via [Send Messages](/api-reference/whatsapp/messages), whichever comes first. ## Errors | Body | When | |---|---| | `(#131009) Parameter value not valid — message_id` | The `wamid` is unknown, older than 7 days, or belongs to a different phone number | | `(#100) Invalid parameter — status` | Anything other than `read` | ## cURL ```bash curl -X POST \ "https://api.splashifypro.com/api/v25.0/$PHONE_NUMBER_ID/messages" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "messaging_product": "whatsapp", "status": "read", "message_id": "wamid.…", "typing_indicator": { "type": "text" } }' ``` ## Notes - **Idempotent.** Calling read on the same `message_id` twice is a no-op; the second call returns 200. - **Read receipts are visible to the customer.** Don't enable them for accounts where you'd rather not signal that a message has been seen yet. - **Typing indicator without read.** Not supported — the typing envelope must accompany a read receipt. ======================================================================== ## Marketing Messages URL: https://partner-docs.splashifypro.com/api-reference/whatsapp/marketing-messages ======================================================================== # Marketing Messages A specialised variant of [Send Messages](/api-reference/whatsapp/messages) for `MARKETING`-category templates. Meta rejects marketing sends from the `/messages` endpoint when frequency caps would be hit; this endpoint accepts the same envelope plus the cap-aware flags. ```http POST /api/v25.0/{PHONE_NUMBER_ID}/marketing_messages Authorization: Bearer pk_live_ Content-Type: application/json ``` ## Request body Same envelope as [Send Messages](/api-reference/whatsapp/messages) for `type: "template"`. The template's category must be `MARKETING`. ```json { "messaging_product": "whatsapp", "to": "+919999999999", "type": "template", "template": { "name": "diwali_promo", "language": { "code": "en_US" }, "components": [ { "type": "body", "parameters": [ { "type": "text", "text": "Aditya" } ] } ] } } ``` ## Response — 200 Identical to [Send Messages](/api-reference/whatsapp/messages). ## Errors In addition to the standard `/messages` errors, marketing sends can return: | Status | Example | |---|---| | `400` | `(#131056) Marketing message limit reached` — recipient has hit their per-day marketing cap | ## cURL ```bash curl -X POST \ "https://api.splashifypro.com/api/v25.0/$PHONE_NUMBER_ID/marketing_messages" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "messaging_product": "whatsapp", "to": "+919999999999", "type": "template", "template": { "name": "diwali_promo", "language": { "code": "en_US" }, "components": [] } }' ``` ## Notes - **Frequency caps are recipient-side.** Meta limits how many marketing messages a recipient can receive across all businesses per day. Hitting the cap returns 400 — do not retry the same `to` until the cap resets. - **Quality impact.** Marketing sends count against the phone number's quality rating. Low engagement (read-rate, block-rate) can flip it to `YELLOW` / `RED` and trigger Meta's send throttles. ======================================================================== ## Media URL: https://partner-docs.splashifypro.com/api-reference/whatsapp/media ======================================================================== # Media Three upload paths and two fetch paths. | What | Endpoint | |---|---| | Upload a file → get a media-id for `/messages` | `POST /{PHONE_NUMBER_ID}/media` | | Upload a file → get a handle for templates / Flows | `POST /{PHONE_NUMBER_ID}/media_handle` | | Fetch media URL by `media_id` from an inbound message | `GET /{PHONE_NUMBER_ID}/media/{MEDIA_ID}` | | Fetch media bytes from a known media URL | `GET /{PHONE_NUMBER_ID}/media?url=…` | ## 1. Create Media ID (for sending media files) ```http POST /api/v25.0/{PHONE_NUMBER_ID}/media Authorization: Bearer pk_live_ Content-Type: multipart/form-data ``` ### Form fields | Field | Notes | |---|---| | `messaging_product` | `whatsapp` | | `type` | MIME type, e.g. `image/png`, `application/pdf`, `video/mp4`, `audio/aac` | | `file` | The file binary | ### Response — 200 ```json { "id": "1234567890" } ``` The returned `id` is the **media-id** you reference in subsequent [Send Messages](/api-reference/whatsapp/messages) calls: ```json { "messaging_product": "whatsapp", "to": "+919999999999", "type": "image", "image": { "id": "1234567890" } } ``` Media IDs are valid for **30 days**. After that, re-upload. ## 2. Create Media Handle (for templates & Flows) Header images / videos used inside message templates and inside Flow JSON need a **handle**, not a media-id. Same upload shape as above, different endpoint: ```http POST /api/v25.0/{PHONE_NUMBER_ID}/media_handle Authorization: Bearer pk_live_ Content-Type: multipart/form-data ``` ### Form fields | Field | Notes | |---|---| | `messaging_product` | `whatsapp` | | `type` | MIME type | | `file` | The file binary | ### Response — 200 ```json { "h": "4::aW1hZ2UvcG5n:ARZ..." } ``` The `h` value is the handle string — paste it into template component definitions (`example.header_handle`) or Flow JSON image nodes. Handles do not expire while the parent template / flow exists. ## 3. Get Media URL from media-id (received in webhook) When you receive an inbound `image` / `audio` / `video` / `document` message, the webhook payload includes a `media_id`. Use it to fetch a temporary download URL: ```http GET /api/v25.0/{PHONE_NUMBER_ID}/media/{MEDIA_ID} Authorization: Bearer pk_live_ ``` ### Response — 200 ```json { "url": "https://lookaside.fbsbx.com/whatsapp_business/attachments/…", "mime_type": "image/jpeg", "sha256": "…", "file_size": 102342, "id": "1234567890", "messaging_product":"whatsapp" } ``` The `url` is short-lived (typically 5-minute TTL) and pre-signed. Download immediately and persist on your side if you need long-term storage. ## 4. Get media bytes from a known URL ```http GET /api/v25.0/{PHONE_NUMBER_ID}/media?url={MEDIA_URL} Authorization: Bearer pk_live_ ``` Use this when you already have the `url` (from step 3) and want to proxy the binary fetch through Splashify rather than hitting the short-lived URL directly. ## Constraints | Type | Max size | Notes | |---|---|---| | Image | 5 MB | `image/jpeg`, `image/png` | | Video | 16 MB | `video/mp4`, `video/3gpp` | | Audio | 16 MB | `audio/aac`, `audio/mp4`, `audio/mpeg`, `audio/amr`, `audio/ogg` | | Document | 100 MB | `application/pdf`, `application/vnd.ms-*`, common office formats | | Sticker | 100 KB (static) / 500 KB (animated) | `image/webp` | ## Errors Same matrix as [Phone Number Details](/api-reference/whatsapp/phone-number-details). ## cURL — upload + fetch ```bash # Upload (media-id, valid 30d) curl -X POST \ "https://api.splashifypro.com/api/v25.0/$PHONE_NUMBER_ID/media" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -F "messaging_product=whatsapp" \ -F "type=image/png" \ -F "file=@/path/to/image.png" # Upload (handle, for templates / Flows) curl -X POST \ "https://api.splashifypro.com/api/v25.0/$PHONE_NUMBER_ID/media_handle" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -F "messaging_product=whatsapp" \ -F "type=image/png" \ -F "file=@/path/to/banner.png" # Fetch URL from inbound media-id curl -X GET \ "https://api.splashifypro.com/api/v25.0/$PHONE_NUMBER_ID/media/$MEDIA_ID" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ======================================================================== ## Message Templates URL: https://partner-docs.splashifypro.com/api-reference/whatsapp/message-templates ======================================================================== # Message Templates CRUD for message templates on a customer's WABA. All endpoints are scoped by `{WABA_ID}`. WABAs that don't belong to a customer under your partner account return 403. ## 1. Create a template ```http POST /api/v25.0/{WABA_ID}/message_templates Authorization: Bearer pk_live_ Content-Type: application/json ``` ### Request body ```json { "name": "order_confirmation", "language": "en_US", "category": "UTILITY", "components": [ { "type": "HEADER", "format": "TEXT", "text": "Order {{1}} confirmed" }, { "type": "BODY", "text": "Hi {{1}}, your order {{2}} has been confirmed and will ship in 24 hours." }, { "type": "FOOTER", "text": "Splashify · Order tracking" }, { "type": "BUTTONS", "buttons": [ { "type": "URL", "text": "Track order", "url": "https://example.com/orders/{{1}}" }, { "type": "QUICK_REPLY","text": "Talk to us" } ] } ] } ``` `category` is one of `MARKETING`, `UTILITY`, `AUTHENTICATION`, `SERVICE`. ### Response — 200 ```json { "id": "964823735155832", "status": "PENDING", "category": "UTILITY" } ``` New templates land in `PENDING`. Meta reviews them; expect `APPROVED` or `REJECTED` within minutes-to-hours. Status updates are delivered through the [`whatsapp.template_status`](/webhooks/whatsapp-events) webhook event. ## 2. List all templates on a WABA ```http GET /api/v25.0/{WABA_ID}/message_templates Authorization: Bearer pk_live_ ``` ### Response — 200 ```json { "data": [ { "name": "order_confirmation", "language": "en_US", "category": "UTILITY", "status": "APPROVED", "id": "964823735155832", "components": [ /* … */ ] } ], "paging": { "cursors": { "before": "...", "after": "..." } } } ``` ## 3. Get one template by ID ```http GET /api/v25.0/{WABA_ID}/message_templates/id/{TEMPLATE_ID} Authorization: Bearer pk_live_ ``` ## 4. Edit a template (category) ```http POST /api/v25.0/{WABA_ID}/message_templates/id/{TEMPLATE_ID} Authorization: Bearer pk_live_ Content-Type: application/json ``` Use this to re-categorise an approved template — for example, moving a template from `UTILITY` to `MARKETING` after a copy change. ```json { "category": "MARKETING" } ``` The template's `status` flips back to `PENDING` for a fresh Meta review. ### Response — 200 ```json { "success": true } ``` ## 5. Edit a template (components) Same endpoint, different body — change the body / header / footer / buttons of an existing template. ```http POST /api/v25.0/{WABA_ID}/message_templates/id/{TEMPLATE_ID} ``` ```json { "components": [ { "type": "HEADER", "format": "TEXT", "text": "Updated header for {{1}}" }, { "type": "BODY", "text": "Hi {{1}}, we've updated your order {{2}} status." }, { "type": "FOOTER", "text": "Splashify · Updated" } ] } ``` Component edits also flip status to `PENDING`. Variables in the new copy must remain contiguous (`{{1}}`, `{{2}}`, …) or Meta rejects. ## 6. Template previews Generate a preview block for an authentication template **before** you create it — useful for showing your customer what their authentication template will look like in WhatsApp. ```http GET /api/v25.0/{WABA_ID}/message_template_previews?category=AUTHENTICATION&languages=en_US&add_security_recommendation=true&code_expiration_minutes=10&button_types=OTP Authorization: Bearer pk_live_ ``` | Param | Notes | |---|---| | `category` | Currently only `AUTHENTICATION` is supported in the preview API | | `languages` | Comma-separated list of language codes | | `add_security_recommendation` | `true` to include "For your security, don't share this code" footer text | | `code_expiration_minutes` | If set, adds an expiry footer note | | `button_types` | `OTP` (one-tap autofill) or `COPY_CODE` | ### Response — 200 ```json { "data": [ { "language": "en_US", "preview": { "body": "{{1}} is your verification code.", "footer": "For your security, do not share this code.", "buttons":[ { "type": "OTP", "text": "Autofill" } ] } } ] } ``` ## 7. Compare templates (analytics) Compare per-template metrics for **2 templates** within a date range. ```http GET /api/v25.0/{TEMPLATE_ID}/compare?template_ids=[]&start=&end= Authorization: Bearer pk_live_ ``` | Param | Notes | |---|---| | `template_ids` | The OTHER template ID to compare against. Only one entry — exactly 2 templates compared at a time (the path one + the query one) | | `start` / `end` | UNIX timestamps. UTC day boundaries | ### Response — 200 ```json { "data": [ { "template_id": "964823735155832", "sent": 125400, "delivered": 118200, "read": 82100, "ctr": 0.045 }, { "template_id": "954638012257287", "sent": 89200, "delivered": 85100, "read": 71400, "ctr": 0.082 } ] } ``` `ctr` is computed by Meta on button-tap analytics for `MARKETING` / `UTILITY` templates. ## 8. Delete a template Two delete shapes — by ID + name (precise), or by name only (deletes all language variants). ### By ID + name (recommended) ```http DELETE /api/v25.0/{WABA_ID}/message_templates?hsm_id=964823735155832&name=order_confirmation Authorization: Bearer pk_live_ ``` Pass **both** `hsm_id` (the template ID) and `name`. Meta uses both to disambiguate when multiple language variants of a template share a name. Deletes that specific variant only. ### By name only ```http DELETE /api/v25.0/{WABA_ID}/message_templates?name=order_confirmation Authorization: Bearer pk_live_ ``` Deletes **all language variants** with this name in a single call. Use with care — there's no undo. ### Response — 200 ```json { "success": true } ``` ## Errors | Status | Example | |---|---| | `400` | Meta returns 400 verbatim for invalid payloads (unknown variable index, oversized header, disallowed URL, etc.) | | `401` | `{ "success": false, "message": "unauthorized" }` | | `403` | `{ "success": false, "message": "id does not belong to your account" }` | | `404` | `{ "success": false, "message": "id not found" }` | | `502` | `{ "success": false, "message": "WhatsApp request failed. Try again shortly." }` | ## cURL — create ```bash curl -X POST \ "https://api.splashifypro.com/api/v25.0/$WABA_ID/message_templates" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "order_confirmation", "language": "en_US", "category": "UTILITY", "components": [ { "type": "BODY", "text": "Hi {{1}}, your order {{2}} is confirmed." } ] }' ``` ## Notes - **Review can take up to 24 hours** during high-volume periods on the Meta side. Avoid baking a new template into a live send queue without a fallback path. - **Re-submitting the same name** in the same language replaces the PENDING/REJECTED variant; an APPROVED variant must be deleted before re-creating. - **Variable indices** must be contiguous (`{{1}}`, `{{2}}`, …). Gaps cause Meta to reject the template at submission. ======================================================================== ## Send Messages URL: https://partner-docs.splashifypro.com/api-reference/whatsapp/messages ======================================================================== # Send Messages Send a WhatsApp message from one of your customers' onboarded phone numbers. ```http POST /api/v25.0/{PHONE_NUMBER_ID}/messages Authorization: Bearer pk_live_ Content-Type: application/json ``` `{PHONE_NUMBER_ID}` is one of the IDs returned by [Phone Numbers](/api-reference/whatsapp/phone-numbers). Phone IDs that don't belong to a customer under your partner account return 403. ## Request body The body is the WhatsApp Cloud API message envelope. The most common shapes: ### Text ```json { "messaging_product": "whatsapp", "recipient_type": "individual", "to": "+919999999999", "type": "text", "text": { "preview_url": false, "body": "Hello from Splashify!" } } ``` ### Template ```json { "messaging_product": "whatsapp", "to": "+919999999999", "type": "template", "template": { "name": "order_confirmation", "language": { "code": "en_US" }, "components": [ { "type": "body", "parameters": [ { "type": "text", "text": "Aditya" }, { "type": "text", "text": "ORD-1234" } ] } ] } } ``` ### Media (image / video / document / audio) ```json { "messaging_product": "whatsapp", "to": "+919999999999", "type": "image", "image": { "link": "https://example.com/banner.png", "caption": "Your order is on the way" } } ``` ### Interactive (buttons, list) ```json { "messaging_product": "whatsapp", "to": "+919999999999", "type": "interactive", "interactive": { "type": "button", "body": { "text": "Choose an option" }, "action": { "buttons": [ { "type": "reply", "reply": { "id": "yes", "title": "Yes" } }, { "type": "reply", "reply": { "id": "no", "title": "No" } } ] } } } ``` All other Cloud API message types (`location`, `contacts`, `sticker`, `reaction`) work the same way — see the WhatsApp Cloud API reference for the exact envelope shape. ## Response — 200 ```json { "messaging_product": "whatsapp", "contacts": [ { "input": "+919999999999", "wa_id": "919999999999" } ], "messages": [ { "id": "wamid.HBgMOTE5OTk5OTk5OTk5FQIAERgSNzg5MTIzNDU2Nzg5MEFCQ0RFAA==", "message_status": "accepted" } ] } ``` The `id` is the `wamid` — store it; subsequent webhook status events (`sent` → `delivered` → `read`) reference it. ## Billing Every accepted send is recorded against your partner wallet. When the delivery webhook lands (or `sent` if delivery never fires), we debit your wallet at the per-category rate (`m` Marketing / `u` Utility / `a` Authentication / `s` Service / `ai` Authentication-International). See [Billing Deductions](/api-reference/billing/deductions) for the ledger and the daily-spend chart. ## Errors | Status | Example | |---|---| | `400` | Meta returns 400 verbatim when the envelope is malformed (missing `to`, unknown `type`, template not found, etc.) | | `401` | `{ "success": false, "message": "unauthorized" }` | | `403` | `{ "success": false, "message": "id does not belong to your account" }` | | `404` | `{ "success": false, "message": "id not found" }` | | `502` | `{ "success": false, "message": "WhatsApp request failed. Try again shortly." }` | ## cURL ```bash curl -X POST \ "https://api.splashifypro.com/api/v25.0/$PHONE_NUMBER_ID/messages" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "messaging_product": "whatsapp", "to": "+919999999999", "type": "text", "text": { "body": "Hello!" } }' ``` ## Notes - **Wallet preflight.** If your partner wallet is depleted and the per-partner deduction toggle is on, sends may be blocked at our layer before reaching Meta. Check the wallet page for current balance + low-balance alerts. - **Webhook fan-out.** Status updates Meta sends back (`sent`/`delivered`/`read`/`failed`) are forwarded to your configured webhook URL with our `whatsapp.message_status` event type — see [Webhook Events](/webhooks/whatsapp-events). ======================================================================== ## Messaging Limit URL: https://partner-docs.splashifypro.com/api-reference/whatsapp/messaging-limit ======================================================================== # Messaging Limit Returns the current messaging tier for one phone number. Meta scales tiers automatically based on quality + activity: ``` TIER_50 → TIER_250 → TIER_1K → TIER_10K → TIER_100K → TIER_UNLIMITED ``` This is a focused `fields=` view of the same endpoint as [Phone Number Details](/api-reference/whatsapp/phone-number-details). ```http GET /api/v25.0/{PHONE_NUMBER_ID}?fields=whatsapp_business_manager_messaging_limit,messaging_limit_tier Authorization: Bearer pk_live_ ``` ## Customer-scoped alias ```http GET /api/v1/partner/customers/{customer_id}/whatsapp/phone-numbers/{phone_number_id}/messaging-limit ``` The alias is a fixed-query shortcut — it injects `fields=whatsapp_business_manager_messaging_limit,messaging_limit_tier` for you. You can still pass your own `fields=` to override. ## Response — 200 ```json { "whatsapp_business_manager_messaging_limit": "TIER_2K", "id": "993857633806592" } ``` | Field | Notes | |---|---| | `whatsapp_business_manager_messaging_limit` | Active tier | | `id` | The phone-number ID you queried | ## Errors Same matrix as [Phone Number Details](/api-reference/whatsapp/phone-number-details). ## cURL ```bash curl -X GET \ "https://api.splashifypro.com/api/v25.0/$PHONE_NUMBER_ID?fields=whatsapp_business_manager_messaging_limit,messaging_limit_tier" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ## Notes - **Tier transitions are gradual.** When Meta promotes a number to a higher tier, the change can take an hour or two to surface here. ======================================================================== ## WhatsApp API — Overview URL: https://partner-docs.splashifypro.com/api-reference/whatsapp/overview ======================================================================== # WhatsApp API — Overview Every WhatsApp Cloud API endpoint sits under `/api/v25.0/{ID}/...`. You authenticate with your Splashify partner API key; we handle the WhatsApp Business platform credentials on our side. ```bash curl https://api.splashifypro.com/api/v25.0/$WABA_ID/phone_numbers \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ## How ID ownership works The first path segment after `/api/v25.0/` is either a **WABA ID**, a **phone-number ID**, a **flow ID**, a **template ID**, or an **ad account ID** (prefixed `act_…`). We: 1. Resolve which kind of ID it is via internal lookups. 2. Confirm it belongs to a customer under your partner account. 3. Forward the call to the WhatsApp Business platform on your behalf. IDs that resolve to a different partner account return `403 — id does not belong to your account`. IDs that don't resolve return `404 — id not found`. ## Endpoint catalog ### Account-level (`{WABA_ID}` as the first segment) | Endpoint | Page | |---|---| | `GET /api/v25.0/{WABA_ID}/phone_numbers` | [Phone Numbers](/api-reference/whatsapp/phone-numbers) | | `GET /api/v25.0/{WABA_ID}?fields=health_status` | [Health Status](/api-reference/whatsapp/health-status) | | `GET /api/v25.0/{WABA_ID}?fields=analytics.…` | [Message Analytics](/api-reference/whatsapp/analytics-messages) | | `GET /api/v25.0/{WABA_ID}?fields=conversation_analytics.…` | [Conversation Analytics](/api-reference/whatsapp/analytics-conversations) | | `POST /api/v25.0/{WABA_ID}?is_enabled_for_insights=true` | [Template Analytics — enable](/api-reference/whatsapp/analytics-templates) | | `GET /api/v25.0/{WABA_ID}?fields=template_analytics?…` | [Template Analytics — read](/api-reference/whatsapp/analytics-templates) | | `GET /api/v25.0/{WABA_ID}?fields=call_analytics.…` | [Voice Calling — analytics](/api-reference/whatsapp/voice-calling#10-call-analytics) | | `POST/GET /api/v25.0/{WABA_ID}/message_templates` | [Message Templates](/api-reference/whatsapp/message-templates) | | `POST /api/v25.0/{WABA_ID}/message_templates/id/{TEMPLATE_ID}` | [Message Templates — edit](/api-reference/whatsapp/message-templates) | | `GET /api/v25.0/{WABA_ID}/message_templates/id/{TEMPLATE_ID}` | [Message Templates — get one](/api-reference/whatsapp/message-templates) | | `DELETE /api/v25.0/{WABA_ID}/message_templates?…` | [Message Templates — delete](/api-reference/whatsapp/message-templates) | | `GET /api/v25.0/{WABA_ID}/message_template_previews?…` | [Message Templates — previews](/api-reference/whatsapp/message-templates) | | `GET /api/v25.0/{TEMPLATE_ID}/compare?…` | [Message Templates — compare](/api-reference/whatsapp/message-templates) | | `POST/GET /api/v25.0/{WABA_ID}/flows` | [Flows](/api-reference/whatsapp/flows) | ### Phone-level (`{PHONE_NUMBER_ID}` as the first segment) | Endpoint | Page | |---|---| | `GET /api/v25.0/{PHONE_NUMBER_ID}?fields=status,…` | [Phone Number Details](/api-reference/whatsapp/phone-number-details) | | `GET /api/v25.0/{PHONE_NUMBER_ID}?fields=whatsapp_business_manager_messaging_limit,messaging_limit_tier` | [Messaging Limit](/api-reference/whatsapp/messaging-limit) | | `POST /api/v25.0/{PHONE_NUMBER_ID}/register` | [Register Number](/api-reference/whatsapp/registration) + [Update Display Name](/api-reference/whatsapp/display-name) | | `POST /api/v25.0/{PHONE_NUMBER_ID}/request_code?code_method=SMS&language=en_US` | [Verification Code](/api-reference/whatsapp/verification-code) | | `POST /api/v25.0/{PHONE_NUMBER_ID}/verify_code` | [Verification Code](/api-reference/whatsapp/verification-code) | | `POST /api/v25.0/{PHONE_NUMBER_ID}/messages` | [Send Messages](/api-reference/whatsapp/messages) / [Mark as Read](/api-reference/whatsapp/mark-as-read) | | `POST /api/v25.0/{PHONE_NUMBER_ID}/marketing_messages` | [Marketing Messages](/api-reference/whatsapp/marketing-messages) | | `GET/POST /api/v25.0/{PHONE_NUMBER_ID}/whatsapp_business_profile` | [Business Profile](/api-reference/whatsapp/business-profile) | | `POST /api/v25.0/{PHONE_NUMBER_ID}/media` | [Media — upload](/api-reference/whatsapp/media) | | `POST /api/v25.0/{PHONE_NUMBER_ID}/media_handle` | [Media — handle (for templates / Flows)](/api-reference/whatsapp/media) | | `GET /api/v25.0/{PHONE_NUMBER_ID}/media/{MEDIA_ID}` | [Media — fetch URL](/api-reference/whatsapp/media) | | `GET /api/v25.0/{PHONE_NUMBER_ID}/media?url=…` | [Media — fetch bytes](/api-reference/whatsapp/media) | | `GET/POST/DELETE /api/v25.0/{PHONE_NUMBER_ID}/message_qrdls` | [QR Codes](/api-reference/whatsapp/qr-codes) | | `POST/GET /api/v25.0/{PHONE_NUMBER_ID}/conversational_automation` | [Conversational Components](/api-reference/whatsapp/conversational-components) | | `GET/POST /api/v25.0/{PHONE_NUMBER_ID}/whatsapp_commerce_settings` | [Commerce](/api-reference/whatsapp/commerce) | | `GET/POST /api/v25.0/{PHONE_NUMBER_ID}/settings` | [Voice Calling — settings](/api-reference/whatsapp/voice-calling) | | `POST /api/v25.0/{PHONE_NUMBER_ID}/calls` | [Voice Calling — initiate](/api-reference/whatsapp/voice-calling) | | `GET /api/v25.0/{PHONE_NUMBER_ID}/call_permissions?user_wa_id=…` | [Voice Calling — permissions](/api-reference/whatsapp/voice-calling) | | `POST /api/v25.0/{PHONE_NUMBER_ID}/groups` | [Groups](/api-reference/whatsapp/groups) | | `POST/GET/DELETE /api/v25.0/{PHONE_NUMBER_ID}/block_users` | [Block / Unblock](/api-reference/whatsapp/block-users) | ### Flow node (`{FLOW_ID}` as the first segment) | Endpoint | Page | |---|---| | `GET/POST /api/v25.0/{FLOW_ID}` | [Flows — update / read](/api-reference/whatsapp/flows) | | `POST /api/v25.0/{FLOW_ID}/assets` | [Flows — upload JSON](/api-reference/whatsapp/flows) | | `POST /api/v25.0/{FLOW_ID}/publish` | [Flows — publish](/api-reference/whatsapp/flows) | | `POST /api/v25.0/{FLOW_ID}/deprecate` | [Flows — deprecate](/api-reference/whatsapp/flows) | ### CTWA / Ads Manager (`act_{AD_ACCOUNT_ID}` or `{CAMPAIGN_ID}/{ADSET_ID}/{CREATIVE_ID}/{AD_ID}` as the first segment) | Endpoint | Page | |---|---| | `POST /api/v25.0/act_{AD_ACCOUNT_ID}/campaigns` | [CTWA — 1. campaign](/api-reference/whatsapp/ctwa) | | `POST /api/v25.0/act_{AD_ACCOUNT_ID}/adsets` | [CTWA — 2. ad set](/api-reference/whatsapp/ctwa) | | `POST /api/v25.0/act_{AD_ACCOUNT_ID}/adcreatives` | [CTWA — 3. creative](/api-reference/whatsapp/ctwa) | | `POST /api/v25.0/act_{AD_ACCOUNT_ID}/ads` | [CTWA — 4. ad](/api-reference/whatsapp/ctwa) | | `POST /api/v25.0/{AD_ID}` | [CTWA — 5. publish](/api-reference/whatsapp/ctwa) | | `GET /api/v25.0/{CAMPAIGN_ID \| ADSET_ID \| CREATIVE_ID \| AD_ID}` | [CTWA — read-back](/api-reference/whatsapp/ctwa) | ### Onboarding | Endpoint | Page | |---|---| | `POST /api/v1/partner/customers/{customer_id}/whatsapp/tp-signup` | [TP-Signup](/api-reference/whatsapp/tp-signup) | ## Authentication ```http Authorization: Bearer pk_live_ ``` If WhatsApp isn't enabled for your account yet, endpoints return 503. Contact support to enable. ## Common errors | Status | Body | When | |---|---|---| | `401` | `{ "success": false, "message": "unauthorized" }` | Missing or invalid partner API key | | `403` | `{ "success": false, "message": "id does not belong to your account" }` | First-segment ID belongs to a different partner's customer | | `404` | `{ "success": false, "message": "id not found" }` | First-segment ID isn't on any partner's roster | | `502` | `{ "success": false, "message": "WhatsApp request failed. Try again shortly." }` | Temporary platform error — retry with jitter | | `503` | `{ "success": false, "message": "WhatsApp integration is not configured for your account. Contact support." }` | WhatsApp not enabled for your account | WhatsApp Cloud API errors from Meta (4xx with their own `code` / `error_subcode`) pass through with their original status. ======================================================================== ## Phone Number Details URL: https://partner-docs.splashifypro.com/api-reference/whatsapp/phone-number-details ======================================================================== # Phone Number Details Detailed status for one phone number. Use [Phone Numbers](/api-reference/whatsapp/phone-numbers) to list them all; this endpoint zooms in on one. ```http GET /api/v25.0/{PHONE_NUMBER_ID}?fields=status,is_official_business_account,id,name_status,code_verification_status,display_phone_number,platform_type,messaging_limit_tier,throughput Authorization: Bearer pk_live_ ``` The `{PHONE_NUMBER_ID}` must belong to a customer under your partner account — IDs that resolve elsewhere return 403. ## Customer-scoped alias ```http GET /api/v1/partner/customers/{customer_id}/whatsapp/phone-numbers/{phone_number_id} ``` Same response. Use whichever shape fits your data model. ## Fields If you don't pass a `fields=` query, Meta returns its default set. The canonical list of useful fields is: ``` status, is_official_business_account, id, name_status, code_verification_status, display_phone_number, platform_type, messaging_limit_tier, throughput ``` Trim by passing only what you need. ## Response — 200 ```json { "status": "CONNECTED", "is_official_business_account": true, "id": "112269058640637", "name_status": "APPROVED", "code_verification_status": "EXPIRED", "display_phone_number": "+91 70215 12345", "platform_type": "CLOUD_API", "throughput": { "level": "HIGH" } } ``` | Field | Notes | |---|---| | `status` | `CONNECTED` / `DISCONNECTED` / `PENDING` | | `is_official_business_account` | True once Meta verifies the business | | `name_status` | `APPROVED` / `PENDING_REVIEW` / `DECLINED` | | `code_verification_status` | `VERIFIED` / `NOT_VERIFIED` / `EXPIRED` | | `platform_type` | `CLOUD_API` (we don't support on-prem) | | `messaging_limit_tier` | Returned when `whatsapp_business_manager_messaging_limit,messaging_limit_tier` is in `fields=`; see [Messaging Limit](/api-reference/whatsapp/messaging-limit) | | `throughput.level` | `HIGH` / `STANDARD` | ## Errors | Status | Example | |---|---| | `401` | `{ "success": false, "message": "unauthorized" }` | | `403` | `{ "success": false, "message": "id does not belong to your account" }` | | `404` | `{ "success": false, "message": "id not found" }` | | `502` | `{ "success": false, "message": "WhatsApp request failed. Try again shortly." }` | ## cURL ```bash curl -X GET \ "https://api.splashifypro.com/api/v25.0/$PHONE_NUMBER_ID?fields=status,is_official_business_account,id,name_status,code_verification_status,display_phone_number,platform_type,messaging_limit_tier,throughput" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ======================================================================== ## Phone Numbers URL: https://partner-docs.splashifypro.com/api-reference/whatsapp/phone-numbers ======================================================================== # Phone Numbers List the WhatsApp phone numbers attached to a WABA. ```http GET /api/v25.0/{WABA_ID}/phone_numbers Authorization: Bearer pk_live_ ``` You authenticate with your Splashify partner API key. We resolve `{WABA_ID}` to the owning customer on your account and return its phone numbers. WABAs that don't belong to your account return 403. ## Customer-scoped alias If you'd rather address the WABA by your internal `customer_id`, this alias does the same lookup for you: ```http GET /api/v1/partner/customers/{customer_id}/whatsapp/phone-numbers ``` Useful when you don't yet know the WABA ID (e.g. between TP-Signup and the first `whatsapp.waba_onboarded` webhook). ## Response — 200 ```json { "data": [ { "id": "1234567890875", "verified_name": "Acme Corp", "display_phone_number": "+91 70215 12345", "code_verification_status": "VERIFIED", "quality_rating": "GREEN", "platform_type": "CLOUD_API", "throughput": { "level": "HIGH" }, "last_onboarded_time": "2023-08-31T10:55:37+0000" } ], "paging": { "cursors": { "before": "...", "after": "..." } } } ``` | Field | Notes | |---|---| | `id` | The phone-number ID — use it as `{PHONE_NUMBER_ID}` in [`/api/v25.0/{PHONE_NUMBER_ID}/messages`](/api-reference/whatsapp/messages) | | `display_phone_number` | The customer-facing number | | `verified_name` | The Meta-verified business name on this number | | `code_verification_status` | `VERIFIED` or `NOT_VERIFIED` | | `quality_rating` | `GREEN`, `YELLOW`, or `RED` — sustained `RED` triggers Meta's quality-based sending limits | | `throughput.level` | `HIGH` or `STANDARD` | | `last_onboarded_time` | When the number was last registered with Meta | ## Errors | Status | Example | |---|---| | `401` | `{ "success": false, "message": "unauthorized" }` | | `403` | `{ "success": false, "message": "id does not belong to your account" }` | | `404` | `{ "success": false, "message": "id not found" }` | | `502` | `{ "success": false, "message": "WhatsApp request failed. Try again shortly." }` | | `503` | `{ "success": false, "message": "WhatsApp integration is not configured for your account. Contact support." }` | ## cURL ```bash curl -X GET \ "https://api.splashifypro.com/api/v25.0/$WABA_ID/phone_numbers" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ## Notes - **Quality rating.** `RED` indicates Meta has flagged the number's send behaviour. Pause marketing sends and reach support before resuming — this is the same signal Meta uses to throttle. ======================================================================== ## QR Codes & Short Links URL: https://partner-docs.splashifypro.com/api-reference/whatsapp/qr-codes ======================================================================== # QR Codes & Short Links Generate a QR code (or short link) that opens a WhatsApp chat with your number when scanned or tapped, optionally with a pre-filled outgoing message. Useful for receipts, posters, packaging, and in-store signage. ## 1. Create a QR code ```http POST /api/v25.0/{PHONE_NUMBER_ID}/message_qrdls Authorization: Bearer pk_live_ Content-Type: application/json ``` ### Request body ```json { "prefilled_message": "Hi, I'd like to know more about your Diwali offer", "generate_qr_image": "PNG" } ``` | Field | Notes | |---|---| | `prefilled_message` | The text auto-populated in the user's compose box (≤ 250 chars). Leave empty for an empty compose box | | `generate_qr_image` | `PNG`, `SVG`, or omit to skip the image | ### Response — 200 ```json { "code": "ABCD1234", "prefilled_message": "Hi, I'd like to know more about your Diwali offer", "deep_link_url": "https://wa.me/message/ABCD1234", "qr_image_url": "https://scontent.fb.com/qr/ABCD1234.png" } ``` | Field | Notes | |---|---| | `code` | Short opaque code — also the key for the update/delete endpoints below | | `deep_link_url` | The `wa.me/message/…` link. Stable for the life of the code | | `qr_image_url` | Temporary URL — download and host the image yourself for long-term use. Expires after ~1 hour | ## 2. List all QR codes on a phone number ```http GET /api/v25.0/{PHONE_NUMBER_ID}/message_qrdls Authorization: Bearer pk_live_ ``` ### Response — 200 ```json { "data": [ { "code": "ABCD1234", "prefilled_message": "Hi, I'd like to know more about your Diwali offer", "deep_link_url": "https://wa.me/message/ABCD1234" } ] } ``` ## 3. Update a QR code (change the prefilled message) ```http POST /api/v25.0/{PHONE_NUMBER_ID}/message_qrdls/{QR_CODE_ID} Authorization: Bearer pk_live_ Content-Type: application/json ``` ```json { "prefilled_message": "Hi, I'd like to redeem the Diwali coupon" } ``` The `code` and `deep_link_url` stay the same — printed posters keep working, the in-WhatsApp prompt changes. ## 4. Delete a QR code ```http DELETE /api/v25.0/{PHONE_NUMBER_ID}/message_qrdls/{QR_CODE_ID} Authorization: Bearer pk_live_ ``` ### Response — 200 ```json { "success": true } ``` Once deleted, the `wa.me/message/{QR_CODE_ID}` link returns a "this link isn't available" page in WhatsApp. There's no undelete. ## Errors | Body | When | |---|---| | `(#100) prefilled_message exceeds 250 characters` | Trim the message | | `(#100) Maximum 100 QR codes per phone number reached` | Delete unused ones first | ## cURL ```bash # Create curl -X POST \ "https://api.splashifypro.com/api/v25.0/$PHONE_NUMBER_ID/message_qrdls" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "prefilled_message": "Hi, I want to book a slot", "generate_qr_image": "PNG" }' # Delete curl -X DELETE \ "https://api.splashifypro.com/api/v25.0/$PHONE_NUMBER_ID/message_qrdls/ABCD1234" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ## Notes - **Codes are case-sensitive.** Don't normalise — `ABCD1234` and `abcd1234` are different codes. - **Per-phone-number limit: 100.** Audit and delete stale codes periodically. The list endpoint paginates if needed. - **No analytics on scans.** Meta does not report scan counts per code; if you need attribution, point the prefilled message at a short URL on your side and log the redirect. ======================================================================== ## Register Number URL: https://partner-docs.splashifypro.com/api-reference/whatsapp/registration ======================================================================== # Register Number Bring a phone number online for sending after you've verified it with [Verification Code](/api-reference/whatsapp/verification-code). The same endpoint also handles display-name changes — see [Update Display Name](/api-reference/whatsapp/display-name). ```http POST /api/v25.0/{PHONE_NUMBER_ID}/register Authorization: Bearer pk_live_ Content-Type: application/json ``` ## Request body ```json { "messaging_product": "whatsapp", "pin": "123456" } ``` | Field | Notes | |---|---| | `messaging_product` | Always `whatsapp` | | `pin` | The 6-digit two-step-verification PIN registered against this phone number during onboarding | | `new_verified_name` | **Optional.** Set this to also update the verified display name in the same call — see [Update Display Name](/api-reference/whatsapp/display-name) | ## Response — 200 ```json { "success": true } ``` After a successful register, the phone number's `status` (see [Phone Number Details](/api-reference/whatsapp/phone-number-details)) flips to `CONNECTED` and the number can send messages. ## Errors | Body | When | |---|---| | `(#136025) Two-step verification pin mismatch` | Wrong `pin`. After 5 failed attempts the number is locked for 12 hours | | `(#131045) Phone number not verified` | The number isn't `VERIFIED` yet — re-run [Verification Code](/api-reference/whatsapp/verification-code) first | | `(#136100) Name change rate-limited` | If you sent `new_verified_name`, name changes are capped at one per 30 days | ## cURL — register ```bash curl -X POST \ "https://api.splashifypro.com/api/v25.0/$PHONE_NUMBER_ID/register" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "messaging_product": "whatsapp", "pin": "123456" }' ``` ## cURL — register and change display name in one call ```bash curl -X POST \ "https://api.splashifypro.com/api/v25.0/$PHONE_NUMBER_ID/register" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "messaging_product": "whatsapp", "pin": "123456", "new_verified_name": "Splashify Pro" }' ``` ======================================================================== ## TP-Signup URL: https://partner-docs.splashifypro.com/api-reference/whatsapp/tp-signup ======================================================================== {/* audit-alias: /organizations/tp-signup/ — the underlying platform endpoint is /v1/organizations/tp-signup/. Our partner-scoped wrapper sits at /api/v1/partner/customers/{customer_id}/whatsapp/tp-signup. */} # TP-Signup Enrol one of your customers' WhatsApp Business Accounts (WABA) into your partner pipeline. Authenticate with your Splashify partner API key (`Authorization: Bearer pk_live_…`). The request body uses the standard tech-partner enrollment shape; we handle the WhatsApp Business platform call on your behalf. After the request succeeds, the actual onboarding completion is asynchronous: a `whatsapp.waba_onboarded` event arrives on your configured account-level [webhook URL](/webhooks/whatsapp-events). At that point your customer's phone numbers are visible through the [Phone Numbers](/api-reference/whatsapp/phone-numbers) endpoint. ```http POST /api/v1/partner/customers/{customer_id}/whatsapp/tp-signup ``` ## Prerequisites - `customer_id` must belong to your partner account. - Your account must have WhatsApp partner integration enabled (set up by our team during onboarding). If not, the endpoint returns 503. ## Request body ```json { "entry": [ { "changes": [ { "value": { "event": "PARTNER_ADDED", "waba_info": { "waba_id": "123456789012345", "solution_id": "987654321012345", "phone_number": "+919999999999", "data_localization_region": "IN" } } } ] } ], "auto_subscribe": false, "object": "tech_partner" } ``` ### Fields | Field | Type | Required | Notes | |---|---|---|---| | `entry[].changes[].value.event` | string | yes | Use `"PARTNER_ADDED"` | | `entry[].changes[].value.waba_info.waba_id` | string | yes | Customer's WABA ID | | `entry[].changes[].value.waba_info.solution_id` | string | yes | Your Solution ID | | `entry[].changes[].value.waba_info.phone_number` | string | yes | E.164 phone number, e.g. `+91...` | | `entry[].changes[].value.waba_info.data_localization_region` | string | yes | 2-letter ISO region code (`IN`, `SG`, etc.) | | `auto_subscribe` | boolean | no | Defaults to `false`. If `true`, your app is auto-subscribed to webhooks for this WABA. | | `object` | string | yes | Always `"tech_partner"` | ## Response — 200 ```json { "success": true } ``` A 200 means the enrollment was accepted for processing — **not** that the WABA is fully onboarded yet. Watch for the [WABA-onboarded webhook](/webhooks/whatsapp-events) on your configured URL. ## Errors | Status | Example | |---|---| | `400` | `{ "success": false, "message": "entry[0].changes[0].value.waba_info.waba_id is required" }` | | `401` | `{ "success": false, "message": "unauthorized" }` | | `404` | `{ "success": false, "message": "customer not found" }` | | `502` | `{ "success": false, "message": "WhatsApp request failed. Try again shortly." }` | | `503` | `{ "success": false, "message": "WhatsApp integration is not configured for your account. Contact support." }` | Errors from the WhatsApp Business platform (4xx) pass through with their original status code. ## cURL ```bash curl -X POST \ "https://api.splashifypro.com/api/v1/partner/customers/$CUSTOMER_ID/whatsapp/tp-signup" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "entry": [{ "changes": [{ "value": { "event": "PARTNER_ADDED", "waba_info": { "waba_id": "123456789012345", "solution_id": "987654321012345", "phone_number": "+919999999999", "data_localization_region": "IN" } } }] }], "auto_subscribe": false, "object": "tech_partner" }' ``` ## Notes - **Authentication.** Use your Splashify partner API key — that's the only credential you ever need to send. - **Idempotency.** Re-submitting the same `waba_id` is safe — the call returns success and the existing link is updated in place. - **Webhook follow-up.** Once the customer completes embedded signup on the WhatsApp side, you receive [`whatsapp.waba_onboarded`](/webhooks/whatsapp-events) at your webhook URL with the resolved `waba_id` and `phone_number_id`. Internal tokens are stripped before delivery. ======================================================================== ## Verification Code URL: https://partner-docs.splashifypro.com/api-reference/whatsapp/verification-code ======================================================================== # Verification Code Request and confirm the OTP that proves you own a phone number being attached to a WABA. Two endpoints. ## 1. Request the code ```http POST /api/v25.0/{PHONE_NUMBER_ID}/request_code?code_method=SMS&language=en_US Authorization: Bearer pk_live_ ``` Parameters go in the **query string**: | Param | Notes | |---|---| | `code_method` | `SMS` or `VOICE` | | `language` | Language tag, e.g. `en_US`, `hi_IN`, `pt_BR`. Determines the language of the voice prompt / SMS text | No request body required. ### Response — 200 ```json { "success": true } ``` The OTP is sent to the phone number on file. SMS typically arrives in under a minute; voice rings within ~30 seconds. ## 2. Verify the code ```http POST /api/v25.0/{PHONE_NUMBER_ID}/verify_code Authorization: Bearer pk_live_ Content-Type: application/json ``` ### Request body ```json { "code": "123456" } ``` ### Response — 200 ```json { "success": true } ``` After verification succeeds, the phone number's `code_verification_status` (see [Phone Number Details](/api-reference/whatsapp/phone-number-details)) flips to `VERIFIED`. You can then call [Register](/api-reference/whatsapp/registration) to bring the number online. ## Errors | Body | When | |---|---| | `(#131045) Phone number verified too many times` | Cool-down period — wait an hour before retrying | | `(#136022) The code provided is incorrect` | Wrong code. After 5 failed verifies the OTP expires | | `(#136023) The code provided is expired` | OTPs expire 10 minutes after `request_code` succeeds | ## cURL ```bash # 1) request — params in query string, no body curl -X POST \ "https://api.splashifypro.com/api/v25.0/$PHONE_NUMBER_ID/request_code?code_method=SMS&language=en_US" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" # 2) verify curl -X POST \ "https://api.splashifypro.com/api/v25.0/$PHONE_NUMBER_ID/verify_code" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "code": "123456" }' ``` ======================================================================== ## WhatsApp Voice Calling URL: https://partner-docs.splashifypro.com/api-reference/whatsapp/voice-calling ======================================================================== # WhatsApp Voice Calling WhatsApp Voice Calling lets your customers tap a call button in chat (user-initiated) and lets you place outbound calls to customers who've granted permission (business-initiated). Calls are voice-only, in-app, billed per call-minute by Meta. ## Eligibility quick-reference | Capability | Requirement | |---|---| | User-initiated (inbound) | None beyond a registered phone number | | Business-initiated (outbound) | ≥ 2,000 business-initiated conversations in a rolling 24h window | | Both | Number must be `CONNECTED` per [Phone Number Details](/api-reference/whatsapp/phone-number-details) | | Permission rules | 1 permission request / contact / 24h. Max 2 requests / 7 days. Approved permission valid 7 days. Max 5 connected calls / 24h. 4 consecutive unanswered calls → permission auto-revoked | | Not available in | USA, Canada, Egypt, Vietnam, Nigeria, Turkey (Meta-side restriction) | ## 1. Enable calling on a number ```http POST /api/v25.0/{PHONE_NUMBER_ID}/settings Authorization: Bearer pk_live_ Content-Type: application/json ``` ```json { "messaging_product": "whatsapp", "calling": { "status": "ENABLED", "call_icon_visibility": "DEFAULT", "callback_permission_status": "ENABLED" } } ``` | Field | Notes | |---|---| | `status` | `ENABLED` / `DISABLED` | | `call_icon_visibility` | `DEFAULT` (show), `DISABLED` (hide) | | `callback_permission_status` | `ENABLED` / `DISABLED` — gates the business-initiated path | ### Response — 200 ```json { "success": true } ``` ## 2. Read calling settings ```http GET /api/v25.0/{PHONE_NUMBER_ID}/settings?fields=calling Authorization: Bearer pk_live_ ``` ### Response — 200 ```json { "calling": { "status": "ENABLED", "call_icon_visibility": "DEFAULT", "callback_permission_status": "ENABLED", "call_hours": { "status": "ENABLED", "timezone": "Asia/Kolkata", "weekly_operating_hours": [ { "day_of_week": "MONDAY", "open_time": "0900", "close_time": "1800" }, { "day_of_week": "TUESDAY", "open_time": "0900", "close_time": "1800" } ], "holiday_schedule": [] }, "sip": { "status": "DISABLED", "servers": [] } }, "id": "112269058640637" } ``` `call_hours` and `sip` are optional sub-settings; the next two sections cover them. ## 3. Call hours (when the call button is active) ```http POST /api/v25.0/{PHONE_NUMBER_ID}/settings Authorization: Bearer pk_live_ Content-Type: application/json ``` ```json { "messaging_product": "whatsapp", "calling": { "call_hours": { "status": "ENABLED", "timezone": "Asia/Kolkata", "weekly_operating_hours": [ { "day_of_week": "MONDAY", "open_time": "0900", "close_time": "1800" }, { "day_of_week": "TUESDAY", "open_time": "0900", "close_time": "1800" }, { "day_of_week": "WEDNESDAY", "open_time": "0900", "close_time": "1800" }, { "day_of_week": "THURSDAY", "open_time": "0900", "close_time": "1800" }, { "day_of_week": "FRIDAY", "open_time": "0900", "close_time": "1800" } ], "holiday_schedule": [ { "date": "2026-10-02", "start_time": "0000", "end_time": "2359" } ] } } } ``` Outside call hours, the in-app call button is hidden and inbound calls fall through to your configured fallback (a text auto-reply via template, typically). ## 4. SIP routing (optional — route calls to your own PBX) ```json { "messaging_product": "whatsapp", "calling": { "sip": { "status": "ENABLED", "servers": [ { "hostname": "sip.example.com", "port": 5060, "transport": "TLS", "username": "splashify-sip", "auth": { "type": "SECRET", "secret_ref": "" } } ] } } } ``` When SIP is on, Meta bridges the WhatsApp call to your SIP server and your existing PBX / contact-centre handles the audio leg. Without SIP, the call rides Meta's WebRTC stack and you handle it through the calling webhook (connect → pre_accept → accept → terminate, below). ## 5. Send a permission request Permission is a one-shot template send. The template's category must be `UTILITY` and it must include a `permission_request` component. ```http POST /api/v25.0/{PHONE_NUMBER_ID}/messages Authorization: Bearer pk_live_ Content-Type: application/json ``` ```json { "messaging_product": "whatsapp", "to": "+919999999999", "type": "template", "template": { "name": "call_permission_request_v1", "language": { "code": "en_US" }, "components": [ { "type": "body", "parameters": [ { "type": "text", "text": "Aditya" } ] } ] } } ``` The customer sees a permission-request button under the template; tapping it sends a `permission_update` event to your calling webhook (see §10 below). Don't keep retrying — the per-contact rate limits above are hard. ## 6. Read current permission status ```http GET /api/v25.0/{PHONE_NUMBER_ID}/call_permissions?user_wa_id=919999999999 Authorization: Bearer pk_live_ ``` ### Response — 200 ```json { "data": [ { "user_wa_id": "919999999999", "permission_status": "APPROVED", "expires_at": "2026-05-27T09:00:00Z", "requested_at": "2026-05-20T09:00:00Z", "approved_at": "2026-05-20T09:01:42Z", "connected_call_count": 1, "remaining_calls_24h": 4 } ] } ``` `permission_status` values: `PENDING`, `APPROVED`, `DECLINED`, `EXPIRED`, `REVOKED`. ## 7. Initiate an outbound call ```http POST /api/v25.0/{PHONE_NUMBER_ID}/calls Authorization: Bearer pk_live_ Content-Type: application/json ``` ```json { "messaging_product": "whatsapp", "to": "+919999999999", "action": "connect", "session": { "sdp_type": "OFFER", "sdp": "" } } ``` Skip the `session` block if you're using SIP routing — Meta bridges to your SIP server. ### Response — 200 ```json { "messaging_product": "whatsapp", "calls": [ { "id": "wacid.HBgMOTE5OTk5OTk5OTk5FQIA…" } ] } ``` Store the `id` — every subsequent action on this call uses it. ## 8. Pre-accept an inbound call (low-latency audio) When you receive an inbound `connect` event on your webhook, send `pre_accept` before `accept` so Meta can negotiate the media path while your agent UI is still picking up: ```http POST /api/v25.0/{PHONE_NUMBER_ID}/calls Authorization: Bearer pk_live_ Content-Type: application/json ``` ```json { "messaging_product": "whatsapp", "action": "pre_accept", "call_id": "wacid.…", "session": { "sdp_type": "ANSWER", "sdp": "" } } ``` ## 9. Accept / Reject / Terminate Same endpoint, three different `action` values: ```json { "messaging_product": "whatsapp", "action": "accept", "call_id": "wacid.…" } { "messaging_product": "whatsapp", "action": "reject", "call_id": "wacid.…" } { "messaging_product": "whatsapp", "action": "terminate", "call_id": "wacid.…" } ``` - **accept** — answer a call you've pre-accepted. Use after the agent UI has confirmed pickup. - **reject** — decline an inbound call. The caller hears a busy tone. - **terminate** — hang up an active call. Same endpoint both directions (caller or callee). ### Response — 200 ```json { "success": true } ``` ## 10. Call analytics Aggregate counts + cost analytics for calls made on your WABA. ```http GET /api/v25.0/{WABA_ID}?fields=call_analytics.start().end().granularity().dimensions() Authorization: Bearer pk_live_ ``` ### Query parameters (nested inside `fields=`) | Field | Required | Notes | |---|---|---| | `start` / `end` | yes | UNIX timestamps | | `granularity` | yes | `HALF_HOUR` / `DAILY` / `MONTHLY` | | `dimensions` | no | Breakdowns: `DIRECTION` (USER/BUSINESS-initiated), `COUNTRY`, `PHONE` | | `phone_numbers` | no | Restrict to specific numbers | ### Response — 200 ```json { "call_analytics": { "granularity": "HALF_HOUR", "data_points": [ { "start": 1756191627, "end": 1756193427, "total_calls": 120, "completed_calls": 98, "failed_calls": 22, "total_duration_seconds": 14820, "avg_duration_seconds": 151.2, "direction": "BUSINESS_INITIATED" } ] }, "id": "115344761664057" } ``` ### cURL ```bash curl -X GET \ "https://api.splashifypro.com/api/v25.0/$WABA_ID?fields=call_analytics.start(1756191627).end(1756209627).granularity(HALF_HOUR).dimensions(DIRECTION)" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ## 11. Call permission templates + call button templates Two specialised template shapes used with calling. Both are created through the standard [Message Templates](/api-reference/whatsapp/message-templates) endpoint with specific component types: ### Call permission template ```json { "name": "call_permission_request_v1", "language": "en_US", "category": "UTILITY", "components": [ { "type": "BODY", "text": "Hi {{1}}, may we call you about your recent order?" }, { "type": "BUTTONS", "buttons": [ { "type": "CALL_PERMISSION_REQUEST", "text": "Allow Calls" } ] } ] } ``` Tapping the button sends a `permission_update` event to your calling webhook (see [Voice Calling §10](/api-reference/whatsapp/voice-calling#10-calling-webhooks)). ### Call button template ```json { "name": "support_with_call_button", "language": "en_US", "category": "UTILITY", "components": [ { "type": "BODY", "text": "Hi {{1}}, your ticket {{2}} is being looked at." }, { "type": "BUTTONS", "buttons": [ { "type": "PHONE_NUMBER", "text": "Call support", "phone_number": "+919876543210" } ] } ] } ``` The `PHONE_NUMBER` button opens a WhatsApp voice call to the listed number when tapped — no permission required, since the user initiates. ### Send a template with a call button Use [Send Messages](/api-reference/whatsapp/messages) with `type: "template"` and reference the template by name. The button parameters slot in like any other template button parameter. ## 12. Calling webhooks Inbound events arrive on your configured webhook URL inside a normal WhatsApp Cloud API envelope, under a `calls` array. ### `connect` — inbound call ringing ```json { "calls": [ { "id": "wacid.…", "from": "919999999999", "to": "919876543210", "event": "connect", "timestamp": "1717000000", "direction": "inbound", "session": { "sdp_type": "OFFER", "sdp": "" } } ] } ``` ### `terminate` — call ended ```json { "calls": [ { "id": "wacid.…", "event": "terminate", "timestamp": "1717000045", "duration_seconds": 38, "status": "COMPLETED", "end_reason": "CALLEE_ENDED" } ] } ``` `status` values: `COMPLETED`, `NO_ANSWER`, `CALLEE_BUSY`, `FAILED`. ### `permission_update` — permission changed ```json { "calls": [ { "event": "permission_update", "user_wa_id": "919999999999", "permission_status": "APPROVED", "expires_at": "2026-05-27T09:00:00Z" } ] } ``` Fires on `APPROVED`, `DECLINED`, `REVOKED`, and on natural expiry. ## Errors | Code | Meaning | |---|---| | `131000` | Account not eligible — under the 2,000-conversations / 24h threshold | | `131001` | No calling permission from this contact | | `131002` | Permission expired (7-day window elapsed) | | `131003` | Call limit exceeded (5 connected calls / 24h) | | `131004` | Contact revoked permission | | `131005` | Call hours closed — outside configured weekly schedule | | `131006` | Call already in progress / invalid `call_id` for action | ## cURL — enable + initiate ```bash # 1) Enable calling on the number curl -X POST \ "https://api.splashifypro.com/api/v25.0/$PHONE_NUMBER_ID/settings" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "messaging_product": "whatsapp", "calling": { "status": "ENABLED", "call_icon_visibility": "DEFAULT", "callback_permission_status": "ENABLED" } }' # 2) Initiate an outbound call (after permission) curl -X POST \ "https://api.splashifypro.com/api/v25.0/$PHONE_NUMBER_ID/calls" \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "messaging_product": "whatsapp", "to": "+919999999999", "action": "connect" }' ``` ## Notes - **Calls do NOT count as conversations** for billing or messaging limits. Meta bills calls separately per minute; see your wallet ledger for the per-region rate. - **SDP exchange is mandatory** for the WebRTC path — without SIP routing, you must run your own WebRTC peer (browser or native) and generate offers / answers. There's no Meta-hosted player. - **Permission templates** must be pre-approved by Meta like any other template. Submit via [Message Templates](/api-reference/whatsapp/message-templates) with a `permission_request` component before sending. - **One call per `phone_number_id`, per recipient, per second.** Bursts above that get rate-limited at the `/calls` endpoint with `code 4`. ======================================================================== ## AI Onboarding URL: https://partner-docs.splashifypro.com/build-with-ai ======================================================================== # AI Onboarding Working with an AI coding assistant (Cursor, Claude Code, ChatGPT, Windsurf, etc.)? Paste this page's URL into your assistant — we've formatted everything below for an LLM to ingest in one shot. For Cursor / Claude Code / Windsurf, run: ``` https://partner-docs.splashifypro.com/getting-started/ai-onboarding ``` ## You are integrating: Splashify Pro Email API You are a developer integration assistant. You are helping a user build against the **Splashify Pro Email API**. The API surface is **AWS-SES-shaped** — endpoint shapes, response field names, and event payloads are aligned with AWS SES so SES integrators feel at home. ## Base URL ``` https://api.splashifypro.com/api/v1/partner/email ``` ## Authentication Every request requires a Bearer API key in the `Authorization` header. Keys are prefixed `pk_live_`. Generate from [partner.splashifypro.com](https://partner.splashifypro.com) → **Settings → API Keys**. ``` Authorization: Bearer pk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ``` ## Core concepts - **Identity:** Verified domain or email address. Required before you can send `From:` that address. Domain identities cover any address at the domain. Verified via 3 DNS records (SPF + DKIM CNAME + DMARC). - **Configuration Set:** A logical grouping of sends. Attach event destinations (webhooks) to a config set; sends specify `configuration_set_name` to route events. AWS-style. - **Event Destination:** Webhook URL on a config set. Receives the AWS-SES-shaped JSON event payload signed with HMAC-SHA256. - **Template:** Reusable email body with `{{variable}}` placeholders. Send via `POST /send-template` or `POST /send-bulk`. - **Suppression list:** Account-level. Hard bounces + complaints + unsubscribes auto-add. Sends to suppressed addresses are rejected pre-SMTP. - **Sandbox:** Default state for new accounts. 200/day cap, 1 send/sec, recipient must be verified. Request production access to lift. - **Wallet:** Prepaid by default. ₹0.01 per email. First 200/day in sandbox are free. Recharge via the partner panel. ## Most-used endpoints ### Send transactional email ```bash POST /api/v1/partner/email/send { "from": "hello@yourcompany.com", "to": ["user@example.com"], "subject": "Hello", "html_body": "

Hi

", "text_body": "Hi", "configuration_set_name": "production", "category": "transactional" } ``` Response: `{"success":true,"results":[{"recipient":"user@example.com","message_id":"","status":"queued"}]}` ### Send templated email ```bash POST /api/v1/partner/email/send-template { "from": "hello@yourcompany.com", "to": ["user@example.com"], "template_name": "welcome", "variables": {"first_name": "Alex"} } ``` ### Bulk templated send (up to 50 recipients × 50 destinations / 500 total) ```bash POST /api/v1/partner/email/send-bulk { "from": "hello@yourcompany.com", "template_name": "newsletter", "default_template_data": {"campaign": "june"}, "destinations": [ { "to": ["a@example.com"], "replacement_data": {"first_name": "Alex"} }, { "to": ["b@example.com"], "replacement_data": {"first_name": "Brett"} } ] } ``` ### Get message status ```bash GET /api/v1/partner/email/emails/{message_id} ``` ### Verify a sending identity (domain) ```bash POST /api/v1/partner/email/identities { "identity_type": "DOMAIN", "identity_value": "yourcompany.com" } ``` Response includes the 3 DNS records to publish (SPF TXT, DKIM CNAME, DMARC TXT). After publishing, trigger: ```bash POST /api/v1/partner/email/identities/DOMAIN/yourcompany.com/verify ``` ### Create a configuration set ```bash POST /api/v1/partner/email/configuration-sets { "name": "production", "suppression_options": "BOUNCE_AND_COMPLAINT" } ``` ### Add a webhook event destination ```bash POST /api/v1/partner/email/configuration-sets/{config_set_id}/event-destinations { "name": "production-webhook", "destination_type": "WEBHOOK", "webhook_url": "https://yourapp.com/webhooks/splashify", "webhook_secret": "your-shared-secret", "matching_event_types": ["send", "delivered", "bounce", "complaint", "open", "click"] } ``` ### Suppression list ```bash GET /api/v1/partner/email/suppression PUT /api/v1/partner/email/suppression/{email} DELETE /api/v1/partner/email/suppression/{email} ``` ### Quotas + reputation ```bash GET /api/v1/partner/email/quotas # daily cap, sandbox, reputation_status GET /api/v1/partner/email/stats # day-by-day counters GET /api/v1/partner/email/reputation # 14d bounce/complaint rate GET /api/v1/partner/email/events # event log ``` ## Webhook payload shape (AWS-SES-style) ```json { "eventType": "Delivery", "mail": { "timestamp": "2026-05-03T12:34:56Z", "messageId": "", "source": "hello@yourcompany.com", "destination": ["user@example.com"] }, "delivery": { "timestamp": "2026-05-03T12:34:57Z", "recipients": ["user@example.com"], "smtpResponse": "250 OK" } } ``` Headers: - `X-Splashify-Event` — `Send|Delivery|Bounce|Complaint|Open|Click|Reject|RenderingFailure|DeliveryDelay` - `X-Splashify-Signature` — `sha256=` HMAC-SHA256 of raw body, key = your `webhook_secret` - `X-Splashify-Timestamp` — Unix seconds - `X-Splashify-Delivery-ID` — UUID per delivery attempt (use for idempotency) Verify the signature in constant time, dedupe on Delivery-ID, return `200 OK` to ack. 4xx = no retry. 5xx + timeout = retry per `{1min, 5min, 15min}`. ## Sandbox vs production Sandbox limits: - 200 emails / day - 1 email / second - Can only send to addresses you've verified Request production access: ```bash POST /api/v1/partner/email/production-access { "use_case": "Transactional emails for our SaaS — signup confirms, password resets, payment receipts", "email_volume_estimate": "5000-15000 per day", "has_unsubscribe_method": true, "has_consent_proof": true } ``` Approval lifts sandbox + bumps daily cap to 50,000 + peak rate to 14/sec. ## Pricing - ₹0.01 per email, flat - First 200/day free in sandbox - Per-partner overrides supported (admin-set) ## Errors All errors are `{success: false, error: "", message: ""}` with a 4xx or 5xx status: - `400` — bad request (read `message`) - `401` — invalid / missing API key - `402` — wallet balance insufficient (recharge) - `403` — sandbox cap, sending paused, IP blocked - `404` — not found - `429` — rate limit (back off) ## Where to read more - Full API reference: https://partner-docs.splashifypro.com/api-reference - Webhooks: https://partner-docs.splashifypro.com/webhooks - Deliverability: https://partner-docs.splashifypro.com/deliverability - Knowledge base: https://partner-docs.splashifypro.com/concepts That's everything an AI agent needs to start integrating. If your assistant asks about something not covered here, link it to the relevant section above. ======================================================================== ## MCP Server URL: https://partner-docs.splashifypro.com/build-with-ai/mcp-server ======================================================================== # MCP Server The **Splashify Pro MCP server** lets your AI assistant (Claude Desktop, Cursor, Windsurf, Claude Code, etc.) call the Email API directly. Send emails, verify identities, configure webhooks — all from a chat conversation. [Model Context Protocol](https://modelcontextprotocol.io) is the open standard Anthropic shipped for connecting AI tools to external APIs. Splashify Pro hosts an MCP server that exposes every public endpoint as a tool the assistant can call. ## Endpoint The MCP server is **hosted** — there's nothing to install locally. Point your AI client at the SSE endpoint and authenticate with your partner API key: ``` URL: https://mcp.splashifypro.com/sse Auth: Authorization: Bearer pk_live_... ``` Use the same `pk_live_…` key you use for the REST API (generate one at [partner.splashifypro.com](https://partner.splashifypro.com) → Settings → API Key). The MCP server uses the same auth chain, permission scope, and rate-limit budget as REST. ## Add to Claude Desktop Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows): ```json { "mcpServers": { "splashifypro": { "type": "sse", "url": "https://mcp.splashifypro.com/sse", "headers": { "Authorization": "Bearer pk_live_..." } } } } ``` Restart Claude Desktop. You'll see "splashifypro" in the bottom-left tools menu. ## Add to Cursor Edit `.cursor/mcp.json` in your project root: ```json { "mcpServers": { "splashifypro": { "type": "sse", "url": "https://mcp.splashifypro.com/sse", "headers": { "Authorization": "Bearer pk_live_..." } } } } ``` ## Add to Claude Code Edit `~/.claude/settings.json` or your project's `.mcp.json`: ```json { "mcpServers": { "splashifypro": { "type": "sse", "url": "https://mcp.splashifypro.com/sse", "headers": { "Authorization": "Bearer pk_live_..." } } } } ``` ## Add to Windsurf Edit `~/.codeium/windsurf/mcp_config.json`. Same JSON shape as Cursor. ## Available tools The MCP server exposes 35+ tools, one per Email API endpoint: | Category | Tools | |---|---| | Send | `partner_email_send`, `partner_email_send_template`, `partner_email_send_bulk`, `partner_email_send_raw`, `partner_email_get_status` | | Identities | `partner_email_list_identities`, `partner_email_create_identity`, `partner_email_get_identity`, `partner_email_verify_identity`, `partner_email_delete_identity` | | Configuration sets | `partner_email_list_configuration_sets`, `partner_email_create_configuration_set`, `partner_email_get_configuration_set`, `partner_email_delete_configuration_set` | | Event destinations | `partner_email_create_event_destination` | | Templates | `partner_email_list_templates`, `partner_email_create_template`, `partner_email_delete_template`, `partner_email_preview_template` | | Suppression | `partner_email_list_suppression`, `partner_email_add_suppression`, `partner_email_remove_suppression` | | Stats + reputation | `partner_email_get_quotas`, `partner_email_get_stats`, `partner_email_get_reputation`, `partner_email_list_events`, `partner_email_get_bounce_report` | | Production access | `partner_email_list_production_access_requests`, `partner_email_submit_production_access` | | Dedicated IP | `partner_email_get_dedicated_ip_request`, `partner_email_request_dedicated_ip`, `partner_email_cancel_dedicated_ip_request` | | Account + security | `partner_email_list_activity_logs`, `partner_email_list_ip_allowlist`, `partner_email_add_ip_allowlist_entry`, `partner_email_remove_ip_allowlist_entry` | ## Example prompts > "Send a welcome email to alex@example.com from hello@mycompany.com" The assistant calls `partner_email_send` with the right shape, hands the `message_id` back to you, and offers to follow up via `partner_email_get_status`. > "Verify the domain mycompany.com and tell me which DNS records I need" `partner_email_create_identity` is called, the response includes the SPF / DKIM / DMARC records, and the assistant explains where to publish them. > "Why are 5% of my emails bouncing?" `partner_email_get_reputation` + `partner_email_list_events?event_type=bounce` fire in parallel; the assistant correlates the bounces against recipients and suggests fixes. ## Security - **Hosted, not local.** Your API key never leaves your machine except as a Bearer header on outbound HTTPS requests to `mcp.splashifypro.com` — same path your REST calls already take. - **Mutating tools confirm first.** Tools that change state (`partner_email_send`, `partner_email_create_*`, `partner_email_delete_*`) generally require the assistant to read back the action; you approve before they fire. - **Audit log.** Every MCP-driven call shows up at [partner.splashifypro.com](https://partner.splashifypro.com) → Activity Log alongside REST + SDK calls so you can see who/what fired which endpoint when. - **Rate limits.** MCP calls share the same per-key budget as REST. Burst protection kicks in identically. ## Troubleshooting - **"splashifypro server not found":** check the JSON config path + restart the host app. SSE transport requires the host app to support remote MCP (Claude Desktop ≥ 0.7, Cursor ≥ 0.42, Claude Code ≥ 1.0). - **`401 invalid_key`:** API key is wrong or revoked. Generate a fresh one at the partner panel. - **`402 insufficient_balance`:** wallet is empty — recharge from the [partner panel](https://partner.splashifypro.com/wallet). - **`429 rate_limited`:** assistant is firing too many requests too fast — back off or apply for production access to lift the per-second cap. ## Feedback + bugs Spotted a bug or want a tool that isn't listed? Email **support@splashifypro.in** with the prompt that triggered it and your `pk_live_` key prefix (first 12 chars). We ship MCP fixes weekly. ======================================================================== ## Common Recipes URL: https://partner-docs.splashifypro.com/build-with-ai/recipes ======================================================================== # Common Recipes Production-grade patterns that save you reinventing the wheel. ## Idempotent transactional sends Replays of the same logical send (e.g. payment-receipt for the same charge ID) shouldn't trigger duplicate emails. Stamp a stable `X-Idempotency-Key` header derived from your business key, and de-dup on your side before calling `/send`: ```js const sentKey = await redis.get(`email:sent:${chargeId}`); if (sentKey) return; // already emailed const res = await client.emails.send({...}); await redis.set(`email:sent:${chargeId}`, res.results[0].messageId, { EX: 86400 }); ``` We don't currently honor an `Idempotency-Key` header server-side (roadmap), so the dedup happens at your layer. ## Per-recipient personalization at scale For bulk sends with per-recipient variables, use `/send-bulk`: ```js await client.emails.sendBulk({ from: "hello@yourcompany.com", templateName: "newsletter-june", defaultTemplateData: { campaign: "june-2026" }, destinations: users.map(u => ({ to: [u.email], replacementData: { first_name: u.firstName, unsubscribe_url: `https://yoursite.com/u/${u.token}`, }, })), }); ``` 50 destinations × 50 recipients × 500 total per request. For larger volumes, chunk into multiple calls — there's no per-account rate limit on bulk-send concurrency, only the per-account per-second peak rate. ## Dynamic from-name Set the `from` field as `"Display Name "` and recipients see "Display Name" in their inbox: ```json { "from": "Sarah from Acme ", ... } ``` Domain still has to be verified. Display name is unverified — recipients see whatever you put there. ## Custom MAIL FROM domain (return-path) Sets the bounce return-path to a custom subdomain so DMARC alignment includes both DKIM and SPF. Lands on roadmap. Until then, the return-path is `bounces@mail.splashifypro.com` and DMARC alignment relies on DKIM only (`p=quarantine` is fine; `p=reject` may need DMARC relaxed alignment). ## Per-customer attribution If you're sending on behalf of multiple downstream customers, create a configuration set per customer and reference it on every send: ```js await client.emails.send({ from: "alerts@yourcompany.com", to: ["customer@example.com"], configurationSetName: "customer_acme_corp", ... }); ``` Stats roll up per config set — `GET /stats?config_set_id=...`. Each config set can also have its own webhook destination so events for Acme go to one URL and events for Globex go to another. ## Hard-bounce auto-list-cleaning Hard bounces are added to your suppression list automatically. To keep your application's email list in sync, listen for the `Bounce` webhook event: ```js app.post("/webhooks/splashify", async (req, res) => { const sig = req.header("x-splashify-signature"); if (!verifyHMAC(req.rawBody, sig, process.env.SPLASHIFY_WEBHOOK_SECRET)) { return res.status(401).end(); } if (req.body.eventType === "Bounce" && req.body.bounce.bounceType === "Permanent") { const email = req.body.bounce.bouncedRecipients[0].emailAddress; await db.users.updateOne({ email }, { $set: { emailBouncedHard: true } }); } res.status(200).end(); }); ``` Now your signup form / re-engagement campaigns can skip these addresses up-front instead of burning send quota. ## Retry on 5xx Network blips and brief upstream issues should retry; auth/quota errors shouldn't. Pseudocode: ```js async function sendWithRetry(payload, attempts = 3) { for (let i = 0; i < attempts; i++) { const res = await client.emails.send(payload); if (res.success) return res; const code = res.error; // Don't retry these — won't get better with time. if (["INVALID_REQUEST", "FROM_NOT_VERIFIED", "INSUFFICIENT_BALANCE", "MISSING_AUTH"].includes(code)) { throw new Error(`${code}: ${res.message}`); } // Backoff on the rest. await sleep(1000 * Math.pow(2, i)); } throw new Error("send_failed_after_retries"); } ``` ## Webhooks that survive your deploys Stamp every event in your local DB before processing — duplicate webhook deliveries (which happen on retries) are idempotent: ```js const eventID = req.header("x-splashify-delivery-id"); const inserted = await db.events.insertOne({ _id: eventID, ...req.body, }, { ignoreDuplicates: true }); if (!inserted.insertedCount) return res.status(200).end(); // dedup // ... process ``` `X-Splashify-Delivery-ID` is a fresh UUID per delivery attempt — even a retried event keeps the same ID. ## Cold-start IP warm-up New production accounts inherit warm sending infrastructure with established reputation across major mailbox providers. You don't need to manually warm up. Dedicated IPs (roadmap) require warm-up — typically 2 weeks of gradually increasing volume. We'll publish a warm-up calculator when dedicated IPs ship. ======================================================================== ## SDKs URL: https://partner-docs.splashifypro.com/build-with-ai/sdks ======================================================================== # SDKs Official SDKs ship for **Node.js**, **Python**, and **PHP**. All three are auto-generated from the same OpenAPI spec the API runs against, so they stay in lock-step with the platform. | Language | Package | Source | |---|---|---| | Node.js | `@splashifypro/sdk` | [github.com/splashifypro/sdk-node](https://github.com/splashifypro/sdk-node) | | Python | `splashifypro` | [github.com/splashifypro/sdk-python](https://github.com/splashifypro/sdk-python) | | PHP | `splashifypro/sdk` | [github.com/splashifypro/sdk-php](https://github.com/splashifypro/sdk-php) | Don't see your language? The API is plain HTTP/JSON — every endpoint can be called with the language's stdlib HTTP client. See the per-language [Quickstarts](/getting-started/node-quickstart) for hand-rolled examples. ## Node.js ```bash npm install @splashifypro/sdk ``` ```js const client = new SplashifyClient({ apiKey: process.env.SPLASHIFY_API_KEY, }); const result = await client.emails.send({ from: "hello@yourcompany.com", to: ["customer@example.com"], subject: "Welcome", htmlBody: "

Welcome

", }); console.log(result.results[0].messageId); ``` TypeScript types ship with the package. Edge runtimes (Vercel, Cloudflare Workers, Deno) are supported. ## Python ```bash pip install splashifypro ``` ```python from splashifypro import SplashifyClient client = SplashifyClient(api_key=os.environ["SPLASHIFY_API_KEY"]) result = client.emails.send( from_="hello@yourcompany.com", to=["customer@example.com"], subject="Welcome", html_body="

Welcome

", ) print(result["results"][0]["message_id"]) ``` Async client also available: ```python from splashifypro import AsyncSplashifyClient async with AsyncSplashifyClient(api_key=...) as client: result = await client.emails.send(...) ``` ## PHP ```bash composer require splashifypro/sdk ``` ```php use Splashifypro\Client; $client = new Client(getenv('SPLASHIFY_API_KEY')); $result = $client->emails->send([ 'from' => 'hello@yourcompany.com', 'to' => ['customer@example.com'], 'subject' => 'Welcome', 'html_body' => '

Welcome

', ]); echo $result['results'][0]['message_id']; ``` Laravel users can pull in our facade by registering the package's service provider — see the README on [github.com/splashifypro/sdk-php](https://github.com/splashifypro/sdk-php). ## Versioning SDKs follow [semantic versioning](https://semver.org). Major version bumps only happen for breaking changes; the API itself is versioned under `/api/v1/` and we'll ship `/api/v2/` before any breaking contract change so the SDK can support both. ## Reporting issues Each SDK lives in its own repo and accepts issues + PRs: - [github.com/splashifypro/sdk-node/issues](https://github.com/splashifypro/sdk-node/issues) - [github.com/splashifypro/sdk-python/issues](https://github.com/splashifypro/sdk-python/issues) - [github.com/splashifypro/sdk-php/issues](https://github.com/splashifypro/sdk-php/issues) For platform-side bugs (the API misbehaving regardless of SDK), or for anything you can't reduce to a single language, email **support@splashifypro.in** with the request payload + the `pk_live_` key prefix (first 12 chars). ======================================================================== ## Knowledge Base URL: https://partner-docs.splashifypro.com/knowledge-base ======================================================================== # Knowledge Base Long-form guides + reference material that goes deeper than the endpoint docs. Read these when you want to understand WHY the API works the way it does — or when you're debugging deliverability. ## Topics - [**Concepts**](/knowledge-base/concepts) — sending identities, configuration sets, suppression lists, sandbox vs production, reputation - [**Deliverability**](/knowledge-base/deliverability) — SPF / DKIM / DMARC, IP warm-up, content best practices, sender reputation - [**Pricing**](/knowledge-base/pricing) — flat ₹0.01 per email, sandbox free tier, billing model - [**Errors**](/knowledge-base/errors) — every error code, what it means, how to fix it - [**FAQ**](/knowledge-base/faq) — common questions ## Quick orientation If you're new, read these in order: 1. [Sending identities](/knowledge-base/concepts/sending-identities) 2. [Configuration sets](/knowledge-base/concepts/configuration-sets) 3. [Sandbox vs production](/knowledge-base/concepts/sandbox) 4. [SPF / DKIM / DMARC](/knowledge-base/deliverability/spf-dkim-dmarc) 5. [Pricing](/knowledge-base/pricing) ======================================================================== ## Concepts URL: https://partner-docs.splashifypro.com/knowledge-base/concepts ======================================================================== # Concepts A short tour of the primitives the API exposes. If you've worked with AWS SES the names map 1:1 — sending identities, configuration sets, suppression lists, all here. ## The 5 primitives ``` ┌─────────────────────────────────────────────────────────┐ │ Your Splashify Pro account │ │ │ │ ┌───────────────┐ ┌───────────────┐ ┌─────────────┐ │ │ │ Identity │ │ Identity │ │ Identity │ │ │ │ (your domain) │ │ (alt addr) │ │ (3rd domain)│ │ │ └───────┬───────┘ └───────┬───────┘ └──────┬──────┘ │ │ │ verified │ verified │ pending │ │ ┌───────┴──────────────────┴─────────────────┴──────┐ │ │ │ Configuration Sets │ │ │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ │ │ "production" │ │ "marketing" │ │ │ │ │ │ ↓ events │ │ ↓ events │ │ │ │ │ │ → webhook A │ │ → webhook B │ │ │ │ │ │ → webhook B │ │ │ │ │ │ │ └──────────────┘ └──────────────┘ │ │ │ └────────────────────────────────────────────────────┘ │ │ ┌────────────────────────────────────────────────────┐ │ │ │ Suppression list (account-wide) │ │ │ │ bounced@example.com angry@example.com ... │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ Wallet: ₹X.XX Sandbox: ON / OFF │ └─────────────────────────────────────────────────────────┘ ``` 1. **[Sending identities](/knowledge-base/concepts/sending-identities)** — verified domains + email addresses you can send `From:`. 2. **[Configuration sets](/knowledge-base/concepts/configuration-sets)** — logical groupings of sends that route events to webhook destinations. 3. **[Suppression list](/knowledge-base/concepts/suppression)** — account-wide blocklist. Auto-populated by hard bounces + complaints + unsubscribes. 4. **[Reputation](/knowledge-base/concepts/reputation)** — your account's bounce + complaint rate. Drives auto-pausing if thresholds breach. 5. **[Sandbox vs production](/knowledge-base/concepts/sandbox)** — sandbox = 200/day cap + verified-only recipients. Production = real volume after a quick review. ## Hierarchy - **Identity** is account-wide. Verifying `acme.com` lets you send from any address at `acme.com`. - **Configuration set** scopes sends — events from sends inside a config set go to that set's webhook destinations. - **Suppression list** is account-wide by default. Each config set can opt to also suppress per-config-set on bounces / complaints via `suppression_options`. ## Send-time flow ``` POST /send (with config_set + from + to) │ ▼ 1. From-domain on a verified identity? ──── no → 400 FROM_NOT_VERIFIED │ yes ▼ 2. Recipient on suppression list? ──── yes → status=rejected │ no ▼ 3. Wallet balance >= ₹0.01? ──── no → 402 INSUFFICIENT_BALANCE │ yes (or in sandbox free tier) ▼ 4. Daily quota not exceeded? ──── no → 429 SANDBOX_QUOTA_REACHED │ yes ▼ 5. Queue the email for delivery │ ▼ 6. Fire "Send" event → webhook destinations │ ▼ 7. SMTP attempt → recipient MX ├── 250 OK → "Delivery" event ├── 5xx hard bounce → "Bounce" event + suppress └── 4xx soft bounce → retry (5min, 30min, 2h) ``` ======================================================================== ## Configuration Sets URL: https://partner-docs.splashifypro.com/knowledge-base/concepts/configuration-sets ======================================================================== # Configuration Sets A **configuration set** is a logical bundle of sends that share event-destination wiring + suppression behaviour. Modelled directly on AWS SES configuration sets. If you're shipping email for a single product, you might never create a config set — defaults work fine. But the moment you have: - **Multiple downstream tenants** (one for each customer) - **Multiple email types** (transactional vs marketing) - **Distinct webhook destinations** per use case ...config sets become essential. ## What a config set carries | Field | Purpose | |---|---| | `name` | Human-friendly identifier you reference on `/send` | | `description` | Free-text notes | | `customer_id` | Optional — partner's downstream end-customer attribution | | `sending_enabled` | Kill switch — disable sends through this set without deleting it | | `reputation_tracking_enabled` | Whether bounces/complaints from this set count toward your reputation rolling window | | `suppression_options` | Which categories auto-add to suppression list: `NONE` / `BOUNCE` / `COMPLAINT` / `BOUNCE_AND_COMPLAINT` | | `tags` | Arbitrary key-value pairs that ship in the webhook `mail.tags` field | ## Create one ```bash curl https://api.splashifypro.com/api/v1/partner/email/configuration-sets \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "production", "description": "Production transactional sends", "suppression_options": "BOUNCE_AND_COMPLAINT", "reputation_tracking_enabled": true, "tags": { "env": "prod", "team": "platform" } }' ``` ## Reference on send ```json { "from": "alerts@yourcompany.com", "to": ["customer@example.com"], "subject": "Payment received", "html_body": "...", "configuration_set_name": "production" } ``` Events from this send fan out to every event destination attached to the `production` config set. ## Multi-tenant pattern If you're running a SaaS that sends email on behalf of customers, create one config set per customer: ```js // On customer signup: const cfg = await client.configurationSets.create({ name: `customer_${customer.id}`, customer_id: customer.id, description: `Email sends for ${customer.name}`, tags: { customer_id: customer.id, plan: customer.plan }, }); // On send: await client.emails.send({ from: customer.fromAddress, to: [recipient], configuration_set_name: `customer_${customer.id}`, ... }); ``` Now per-customer stats roll up cleanly via: ```bash curl 'https://api.splashifypro.com/api/v1/partner/email/stats?config_set_id=...' \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` And per-customer event destinations let each customer have their own webhook URL. ## Event destinations A config set without event destinations still records events internally — they show up in `GET /events` and `GET /stats` — but nothing fans out to your servers. Add a webhook destination: ```bash curl https://api.splashifypro.com/api/v1/partner/email/configuration-sets/$CFG/event-destinations \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "production-webhook", "destination_type": "WEBHOOK", "webhook_url": "https://yourapp.com/webhooks/email", "webhook_secret": "...", "matching_event_types": ["send", "delivered", "bounce", "complaint", "open", "click"] }' ``` Multiple destinations per set are fine — useful for routing engagement events to one URL and deliverability events to another. ## Disabling a config set ```bash curl -X PATCH https://api.splashifypro.com/api/v1/partner/email/configuration-sets/$CFG \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -d '{"sending_enabled": false}' ``` Sends with `configuration_set_name` matching this set get `status: rejected` until re-enabled. In-flight sends complete. ## Suppression options | Value | Behaviour | |---|---| | `NONE` | Auto-suppression OFF for this set's sends | | `BOUNCE` | Hard bounces auto-add to suppression list | | `COMPLAINT` | Complaints auto-add to suppression list | | `BOUNCE_AND_COMPLAINT` | Both auto-add (default + recommended) | Manual suppressions via `PUT /suppression/:email` always go to the account-wide list regardless of config-set settings. ## Reputation tracking When `reputation_tracking_enabled: false`, bounces + complaints from this set's sends are excluded from your account's rolling reputation calculation. Useful for: - Test campaigns where you knowingly send to low-quality addresses - Risk-isolated experiments Defaults to `true`. Don't turn this off unless you really mean it. ## Limits - 100 configuration sets per partner account - 5 event destinations per configuration set - Names are unique per partner, case-sensitive, alphanumeric + `_` + `-`, 1-256 chars ======================================================================== ## Reputation URL: https://partner-docs.splashifypro.com/knowledge-base/concepts/reputation ======================================================================== # Reputation Mailbox providers (Gmail, Outlook, Yahoo, Apple Mail) decide whether to deliver your mail to inbox, junk, or refuse it entirely based on **sender reputation** — a continuously-updated score derived from your bounce rate, complaint rate, content patterns, and recipient engagement. We track two of the load-bearing signals: - **Bounce rate** — `bounced / sent` over the rolling 14-day window - **Complaint rate** — `complained / sent` over the rolling 14-day window Both are computed from the per-day counter table behind `GET /partner/email/stats`. ## Status thresholds ``` bounce > 10% OR complaint > 0.5% → PAUSED (auto-pause) bounce > 5% OR complaint > 0.1% → AT_RISK (warning) else → HEALTHY ``` These match AWS SES and the broader industry-standard "watch-list" thresholds. ## What happens at each threshold ### `HEALTHY` Default state. No special handling. ### `AT_RISK` - Email notification to the partner contact - Banner on the partner panel dashboard - Recommendation to investigate recent campaigns + clean lists - No automatic action against your sending — but you should fix the underlying issue immediately ### `PAUSED` - All `/send` calls return `403 SENDING_PAUSED` - The reputation status reflects on `GET /partner/email/quotas` - Your panel dashboard shows the alert + reason - An admin reviews + decides whether to: - Drop you back to sandbox (so you can test fixes) - Re-enable with a stern warning - Keep paused pending list-cleaning evidence To resume sending after PAUSED: 1. Identify the breach source (which campaigns / list segments caused it) 2. Clean the list — remove every address that bounced / complained 3. Reach out to support with your remediation plan 4. Admin can lift the pause via the partner panel ## What we don't track (yet) - **Engagement signals** (open rate, reply rate, forward rate) — on roadmap for shared-IP reputation; today these only matter for dedicated-IP customers. - **Spam-filter scores** (SpamAssassin etc.) — you control content; our infrastructure handles authentication. - **Domain age / DMARC alignment** — hardened automatically through the verification flow. ## Per-config-set vs account-wide By default reputation is computed at the **account level**. Sends across all config sets contribute to the same rolling window. If you want to isolate a high-risk experiment from your main reputation, set `reputation_tracking_enabled: false` on that config set. Bounces + complaints from those sends are still recorded in your event log + suppression list, but excluded from the rolling reputation calculation. Use sparingly — `false` is an opt-out from a healthy default. ## Inspecting your reputation ```bash curl https://api.splashifypro.com/api/v1/partner/email/reputation \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` Response: ```json { "success": true, "window_days": 14, "sent": 12500, "bounced": 218, "complained": 4, "bounce_rate": 0.01744, "complaint_rate": 0.00032, "status": "HEALTHY" } ``` `/quotas` returns the same status alongside daily-quota info — use that endpoint as the single read-once-per-page source on your dashboard. ## How to keep reputation healthy 1. **Send to opt-in lists only.** Buying lists or scraping email addresses is the #1 cause of complaint-rate breaches. 2. **Honor unsubscribes within 10 days.** It's a CAN-SPAM / CASL / DPDP requirement AND it's how you avoid complaint spikes. 3. **Verify before sending.** Hitting hundreds of stale addresses once spikes your bounce rate hard. 4. **Warm up new identities slowly.** Mailbox providers throttle first-contact volume — start with low volume to high-engagement recipients, ramp over a week or two. 5. **Watch your DMARC reports.** Misaligned mail (sent through us but with wrong From-domain) gets quarantined or rejected — that shows up as bounces in our stats. ======================================================================== ## Sandbox vs Production URL: https://partner-docs.splashifypro.com/knowledge-base/concepts/sandbox ======================================================================== # Sandbox vs Production Every new partner account starts in **sandbox mode**. Sandbox is a deliberately constrained version of production where: | Limit | Sandbox | Production (default) | |---|---|---| | Daily send quota | **200 emails/day** | 50,000 emails/day | | Peak send rate | **1 email/second** | 14 emails/second | | Recipient restrictions | Verified-recipient addresses only (TODO; today: cap-only) | Anyone | | Per-email price | First 200/day **free** | ₹0.01/email from email #1 | | All other features | Identical to production | — | Sandbox is NOT a free trial. It's a probation period — we want to see you can verify a domain, configure webhooks, and send a few test emails without making the platform's shared IP reputation worse. ## Why we ship sandbox by default In our first year of operation, ~30% of new accounts caused some form of deliverability damage in week 1. Reasons: - Buying / scraping lists - Testing in production with bogus recipients - Misconfigured templates that 100% bounced Sandbox protects everyone. Once you've shown a clean signal — even just a handful of successful sends + a verified domain — production access is fast. ## What still works in sandbox Everything at the API level. You can: - Verify domain identities + email-address identities - Create configuration sets + event destinations + templates - Send up to 200/day to whoever - Receive webhooks normally - Add suppression entries + check stats + check reputation The constraints are **velocity-only** — daily quota + rate limit. The functional surface is identical. ## Requesting production access ```bash curl https://api.splashifypro.com/api/v1/partner/email/production-access \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "use_case": "We send transactional emails for our SaaS app — signup confirmation, password reset, payment receipts, weekly digest. ~5000 emails/day across all customers.", "email_volume_estimate": "5000-15000 per day", "has_unsubscribe_method": true, "has_consent_proof": true }' ``` Required fields: | Field | What we want | |---|---| | `use_case` | A real description (≥30 chars). Says WHO you're sending to + WHY they signed up | | `email_volume_estimate` | Realistic daily volume. Don't overstate; quotas can be raised later | | `has_unsubscribe_method` | Confirm you have an unsubscribe link in marketing emails | | `has_consent_proof` | Confirm recipients opted in (signup, double-opt-in, purchase, etc.) | All four must be present. Faking them violates our AUP — we audit periodically + downgrade accounts that lied. ## Review timeline Most requests are reviewed within **24 business hours**. If your request: - Has a clear use case + verified domain + zero suppression-list entries → typically approved within an hour - Has a vague use case OR no verified domain yet → comes back with questions - Looks like list-buying / cold outreach → denied with reason You can resubmit any number of times after a denial — fix the flagged issue + try again. ## What approval changes ``` sandbox: true → sandbox: false daily_send_quota: 200 → 50,000 peak_send_rate_per_second: 1 → 14 ``` These are baseline production numbers. For higher volume reach out to support — caps can be raised on a per-partner basis. ## Inspecting your status ```bash curl https://api.splashifypro.com/api/v1/partner/email/quotas \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` Returns: ```json { "daily_send_quota": 200, "peak_send_rate_per_second": 1, "sandbox": true, "sent_today": 47, "sandbox_free_used_today": 47, "reputation_status": "HEALTHY", ... } ``` ## Common denial reasons - **No use case description.** Empty + 1-line submissions are declined unread. - **No verified domain.** We require at least one domain identity in `VERIFIED` status before approving. Sandbox is a 5-minute exercise — verify one + resubmit. - **Vague description.** "Sending email to our users" isn't enough. Tell us WHAT kind of email + HOW recipients opted in. - **Unsubscribe gap.** Marketing email without an unsubscribe link is illegal under CAN-SPAM, CASL, DPDP. Fix it before requesting. - **Cold-list pattern.** "We bought a list of 100K emails" is a decline. ======================================================================== ## Sending Identities URL: https://partner-docs.splashifypro.com/knowledge-base/concepts/sending-identities ======================================================================== # Sending Identities A **sending identity** is anything you've proven you control: - A **domain** (preferred — covers any address at that domain), or - A specific **email address** (limited to that one mailbox) Every email you send must have a `From:` address that matches a **verified** identity. Sending from an unverified address returns `400 FROM_NOT_VERIFIED`. ## Why verification? Without verification, anyone could claim to send from `security@yourbank.com` through our infrastructure. Verification proves you control the domain or address at the DNS / mailbox level. This is the same reason AWS SES, SendGrid, Postmark, Mailgun, and every other ESP requires it. It's not optional in 2026's email landscape. ## Domain verification Verifying a domain takes 3 DNS records. Once published + checked, you can send from **any address** at the domain — `hello@`, `alerts@`, `noreply@`, etc. Create the identity: ```bash curl https://api.splashifypro.com/api/v1/partner/email/identities \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{"identity_type": "DOMAIN", "identity_value": "yourcompany.com"}' ``` The response gives you the 3 records to publish: | Type | Hostname | Value | |---|---|---| | TXT | `yourcompany.com` | `v=spf1 include:_spf.mail.splashifypro.com ~all` | | CNAME | `splashify._domainkey.yourcompany.com` | `splashify._domainkey.mail.splashifypro.com` | | TXT | `_dmarc.yourcompany.com` | `v=DMARC1; p=quarantine; rua=mailto:dmarc@splashifypro.com` | After publishing on your DNS provider, trigger a re-check: ```bash curl -X POST https://api.splashifypro.com/api/v1/partner/email/identities/DOMAIN/yourcompany.com/verify \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` Once `"status": "VERIFIED"` lands, you're good. We re-check verified domains every 24 hours; if any record disappears, status flips back to `PENDING` and we email you. ## Email-address verification For low-volume use cases or when you can't add DNS records (you're using a public-domain mailbox like Gmail), verify a single address: ```bash curl https://api.splashifypro.com/api/v1/partner/email/identities \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{"identity_type": "EMAIL_ADDRESS", "identity_value": "alerts@yourcompany.com"}' ``` We email a verification link to the address. Click it (or use the verify endpoint directly with the included token) and the address becomes verified. > **Public-domain caveat:** We refuse to verify addresses at common > public providers (`gmail.com`, `yahoo.com`, `outlook.com`, etc.) > — sending bulk through `you@gmail.com` violates Google's TOS and > would get our IP blocklisted within hours. Use your own domain. ## Display name vs verified address You can set a friendly display name without verifying it: ```json { "from": "Sarah from Acme " } ``` `acme.com` must be verified. `Sarah from Acme` is unverified — recipients see whatever string you supply. ## Multiple domains Verify as many domains as you like. Common pattern: one for transactional (`mail.acme.com`), one for marketing (`news.acme.com`). Helps deliverability — bounces / complaints on the marketing domain don't drag down transactional reputation. ## Listing your identities ```bash curl https://api.splashifypro.com/api/v1/partner/email/identities \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ## Removing an identity ```bash curl -X DELETE https://api.splashifypro.com/api/v1/partner/email/identities/DOMAIN/yourcompany.com \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` In-flight sends from that domain complete normally; new sends are rejected with `FROM_NOT_VERIFIED`. ## Auto-recheck behaviour Verified domains: re-checked every 24 hours. Pending domains: re-checked every 1 hour for 7 days, then drop to `FAILED` if all three records still aren't published. You can always re-trigger via `POST /verify`. ## Read more - [SPF / DKIM / DMARC explainer](/knowledge-base/deliverability/spf-dkim-dmarc) - [API reference: identities](/api-reference/identities/create) ======================================================================== ## Suppression List URL: https://partner-docs.splashifypro.com/knowledge-base/concepts/suppression ======================================================================== # Suppression List The suppression list is your account's blocklist of recipient addresses we won't send to. It exists to protect: - **Your reputation** — repeated bounces / complaints tank your bounce + complaint rate, which mailbox providers use as a primary signal. - **The recipient** — opting out, manually unsubscribing, or marking as spam should mean they never hear from you again. - **The platform** — collectively low bounce/complaint rates keep the platform's deliverability healthy across all senders. ## What gets auto-added | Trigger | Reason | |---|---| | Hard bounce | Permanent delivery failure (no such user, mailbox terminated, etc.) | | Complaint | Recipient marked the email as spam (FBL report from inbox provider) | | Unsubscribe | Recipient clicked a one-click unsubscribe (RFC 8058) link | Soft bounces (mailbox full, server temporarily unavailable, etc.) do NOT auto-suppress — we retry up to 3 times, then mark the row failed without suppressing. ## What we DON'T auto-add - Single soft bounces (we retry first) - API rejections (e.g. invalid recipient format) — those don't reach SMTP - Greylist 4xx responses (we retry per-domain backoff) ## Behaviour at send time When you call `/send`, every recipient is checked against the suppression list **before** any SMTP attempt. Recipients on the list: - Get `status: rejected` in the per-recipient response - Don't burn wallet balance — you're not billed for rejected recipients - Generate a `Reject` webhook event with `reason: "suppressed"` This matters at scale — sending a bulk to a list with 5% suppressed addresses saves you 5% of the wallet hit + keeps your bounce rate clean. ## Listing the suppression list ```bash curl 'https://api.splashifypro.com/api/v1/partner/email/suppression?reason=BOUNCE&limit=100' \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` Filters: `reason` (BOUNCE / COMPLAINT / UNSUBSCRIBE / MANUAL), `search` (substring), `limit` (1-1000). ## Adding manually ```bash curl -X PUT https://api.splashifypro.com/api/v1/partner/email/suppression/legal-hold@example.com \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{"reason": "MANUAL", "details": "GDPR erasure request 2026-05-03"}' ``` Use cases: - GDPR / DPDP erasure requests - Customer-side opt-outs that didn't come through your unsubscribe flow - Known-bad addresses you want to skip preemptively ## Removing ```bash curl -X DELETE https://api.splashifypro.com/api/v1/partner/email/suppression/customer@example.com \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` Removal lets you re-send to that address. Be conservative — if the address bounced hard or complained, removing it and re-sending is likely to get you suppressed again + drag your reputation down. ## Per-config-set suppression scope By default, suppressions are account-wide. Each config set can override which categories auto-suppress via the `suppression_options` field: | Value | What gets auto-suppressed | |---|---| | `NONE` | Nothing (rare — you're saying "send everything regardless") | | `BOUNCE` | Hard bounces only | | `COMPLAINT` | Complaints only | | `BOUNCE_AND_COMPLAINT` | Both (recommended default) | The check at send time still hits the account-wide list — config- set settings only affect what's auto-WRITTEN to the list. ## Best practices - **Don't programmatically remove suppressions in bulk.** Each removed entry that re-bounces hurts you twice. - **Sync to your application's user table.** Listen to `Bounce` + `Complaint` webhooks, mark those users `email_unsubscribed: true` in your DB, and skip them in your own outbound logic. Defense in depth. - **Honor unsubscribe requests instantly.** If a customer clicks unsubscribe in your app's preferences page, push their email to our suppression list via PUT — don't wait for the next campaign to filter them. - **Audit periodically.** Run `GET /suppression?reason=BOUNCE` once a month and cross-reference against your active customer list — bounced addresses on your billing roster mean broken support delivery + missed renewal emails. ======================================================================== ## Deliverability URL: https://partner-docs.splashifypro.com/knowledge-base/deliverability ======================================================================== # Deliverability Sending email isn't enough. **Inboxing** is. Mailbox providers (Gmail, Outlook, Yahoo, Apple Mail, etc.) decide whether your message lands in inbox, junk, or gets refused based on a stack of signals — domain authentication, sender reputation, content patterns, recipient engagement. This page is the field guide. ## The 90% rule Most deliverability problems come from one of three causes: 1. **Authentication is broken** — SPF/DKIM/DMARC misconfigured → provider can't verify the sender → marks as spam. 2. **List quality is bad** — too many bounces (sending to dead addresses) or too many complaints (recipients didn't expect the email). 3. **Content tripped a spam filter** — links in the body to blocklisted domains, misleading subject lines, missing plaintext alternative. Get authentication right + send to opt-in lists + don't write spammy copy and you'll inbox. ## SPF / DKIM / DMARC The three DNS-level authentication mechanisms every modern inbox provider expects. ### SPF (Sender Policy Framework) A TXT record on your domain that lists IPs / providers authorized to send mail "from" your domain. We give you: ``` TXT yourcompany.com "v=spf1 include:_spf.mail.splashifypro.com ~all" ``` The `include:` mechanism delegates SPF lookup to our published record, so when our IPs change you don't have to update yours. ### DKIM (DomainKeys Identified Mail) A cryptographic signature in the email header that lets the receiver verify the message wasn't tampered with in transit. We publish the public key under our domain and you publish a CNAME that points at it: ``` CNAME splashify._domainkey.yourcompany.com splashify._domainkey.mail.splashifypro.com ``` This way you don't manage private keys + we can rotate the key periodically without coordinating with every customer. ### DMARC (Domain-based Message Authentication, Reporting & Conformance) DMARC sits on top of SPF + DKIM. It tells the receiver what to do with unauthenticated mail claiming to be from your domain: ``` TXT _dmarc.yourcompany.com "v=DMARC1; p=quarantine; rua=mailto:dmarc@splashifypro.com" ``` | Policy | Behaviour | |---|---| | `p=none` | Monitor only — receivers report but don't act | | `p=quarantine` | Send to spam (recommended) | | `p=reject` | Refuse outright (strongest, but risky if any legitimate sender isn't authenticated) | The `rua=` address receives aggregate reports. We provide a public endpoint at `dmarc@splashifypro.com` so you don't need to set up your own. ### Verify all three pass ```bash curl https://api.splashifypro.com/api/v1/partner/email/identities/DOMAIN/yourcompany.com \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` Response includes per-record `pass: true/false`: ```json { "dns_records": { "spf": { "pass": true, "found": "v=spf1 include:_spf.mail.splashifypro.com ~all" }, "dkim": { "pass": true, "found": "CNAME splashify._domainkey.mail.splashifypro.com" }, "dmarc": { "pass": true, "found": "v=DMARC1; p=quarantine; rua=mailto:..." } } } ``` ## List hygiene Bounces and complaints are the #2 cause of deliverability decline. ### Acquire opt-in addresses only Single opt-in (email entered on a form, no confirmation step) is the minimum. Double opt-in (form + confirmation email + click the link) is the gold standard — drops bounce rates ~10x. Don't: - Buy lists - Scrape websites - Import a list of "all my contacts" from a co-founder's old job - Email people who gave you their card at a conference (without saying "we'll add you to our newsletter") ### Bounce + complaint handling Both are auto-managed by the [suppression list](/knowledge-base/concepts/suppression). Hard bounces and complaints get added immediately, and we never attempt to send to suppressed addresses. Sync to your application: - `Bounce` webhook event → mark user `email_bounced=true` in your DB - `Complaint` webhook event → mark user `email_complained=true` in your DB Skip these users from any future outbound — your suppression list is mirrored at our level, but having it locally lets you also skip them from marketing campaigns + product onboarding flows. ### Engagement-based pruning Mailbox providers weight engagement heavily. If a recipient hasn't opened or clicked any of your emails in 6 months, sending to them hurts your reputation. Drop them from active campaigns until they re-engage (e.g. via a "we miss you" prompt). ## Content best practices | Do | Don't | |---|---| | Plain `From:` (`hello@yourcompany.com`) | Display-name-only fakery (`Hello `) | | Clear subject (no `RE:` / `FWD:` if not actually a reply) | All-caps, all-emojis, money symbols | | Both HTML + plaintext body | HTML only — spam filters penalize | | Unsubscribe link in marketing email | Marketing email without unsubscribe (illegal in most jurisdictions) | | Image alt text | Image-only emails (heavy spam signal) | | Concise body | 50% link / 50% text ratio | We auto-generate plaintext alternative from your HTML if you don't provide one. We also auto-inject the unsubscribe link if your template doesn't include one (CAN-SPAM compliance). ## Sender reputation We track [reputation](/knowledge-base/concepts/reputation) at the account level. Bounce rate + complaint rate over rolling 14 days. Above-threshold rates trigger automatic warnings + eventual auto-pause. The platform's sending infrastructure is well-warmed and has good standing across major mailbox providers. You inherit that reputation when you start sending — but bad behaviour from your account hurts the broader platform reputation, which is why we're strict about list quality. ## Subdomain strategy For organisations sending high volume of mixed mail types, use subdomains to isolate reputation: | Domain | Use | |---|---| | `mail.acme.com` | Transactional (signup, receipts, password reset) | | `news.acme.com` | Marketing campaigns / newsletters | | `notify.acme.com` | App notifications (high frequency, low engagement) | Each gets its own verified identity. Bounces / complaints on `news.acme.com` don't drag down `mail.acme.com`'s reputation. ## Read more - [SPF / DKIM / DMARC explainer](/knowledge-base/concepts/sending-identities) - [Reputation thresholds + auto-pause](/knowledge-base/concepts/reputation) - [Suppression list mechanics](/knowledge-base/concepts/suppression) ======================================================================== ## Errors URL: https://partner-docs.splashifypro.com/knowledge-base/errors ======================================================================== # Errors Every error from the Email API has a stable `error` code + a human-readable `message`. Build retry / error-handling logic against the code, not the message. ## Response shape ```json { "success": false, "error": "INSUFFICIENT_BALANCE", "message": "Wallet balance ₹0.00 — recharge to continue sending." } ``` `success: false` is always present. `error` is the stable code. `message` is for display + may change for clarity over time. ## 400 Bad Request | Code | Cause | Fix | |---|---|---| | `INVALID_REQUEST` | Malformed JSON or missing required field | Read `message` | | `INVALID_AMOUNT` | Non-positive amount on a wallet recharge | Pass amount > 0 | | `INVALID_PARAM` | Path / query param doesn't parse | Check the URL | | `MISSING_PARAM` | Required param missing | Read `message` | | `FROM_NOT_VERIFIED` | `from` address not on a verified identity | Verify domain via `POST /identities` | | `BILLING_INCOMPLETE` | Partner profile missing required fields | Fill `PUT /partner/self/billing` | | `BILLING_NOT_SET_UP` | Zoho customer not yet created | Save billing once via `PUT /partner/self/billing` | | `INVALID_SIGNATURE` | HMAC check on payment-verify failed | Don't tamper with payment widget output | | `BAD_RECIPIENT` | Recipient address malformed | Validate before send | | `TOO_MANY_RECIPIENTS` | Per-message recipient cap exceeded (50) | Split into multiple sends | ## 401 Unauthorized | Code | Cause | Fix | |---|---|---| | `MISSING_AUTH` | No `Authorization` header | Add `Authorization: Bearer pk_live_...` | | `INVALID_KEY_FORMAT` | Key doesn't match `pk_live_...` | Regenerate from partner panel | | `KEY_NOT_FOUND` | Key was revoked or never existed | Regenerate | | `KEY_INACTIVE` | Account suspended | Contact support | ## 402 Payment Required | Code | Cause | Fix | |---|---|---| | `INSUFFICIENT_BALANCE` | Wallet balance < send price | Recharge wallet | ## 403 Forbidden | Code | Cause | Fix | |---|---|---| | `IP_BLOCKED` | Your IP isn't on your account's IP allowlist | Add to allowlist or call from an allowed IP | | `SENDING_PAUSED` | Account paused (admin OR reputation auto-pause) | Contact support / clean lists | | `SANDBOX_QUOTA_REACHED` | Daily 200/day sandbox cap hit | Request production access | | `FEATURE_LOCKED` | Plan doesn't include this feature | Upgrade plan | ## 404 Not Found | Code | Cause | Fix | |---|---|---| | `PARTNER_NOT_FOUND` | Partner ID doesn't exist | Check the ID | | `IDENTITY_NOT_FOUND` | Identity hasn't been created | Create via `POST /identities` | | `CONFIG_SET_NOT_FOUND` | Configuration set name unknown | Check name spelling | | `TEMPLATE_NOT_FOUND` | Template name not registered | Create via `POST /templates` | | `MESSAGE_NOT_FOUND` | message_id doesn't match any send | Check the ID + day_bucket | ## 409 Conflict | Code | Cause | Fix | |---|---|---| | `EMAIL_ALREADY_REGISTERED` | Signup with already-used email | Use forgot-password to recover | | `IDENTITY_EXISTS` | Already verified — duplicate POST | Idempotent — existing row returned | | `CONFIG_SET_NAME_TAKEN` | Another set with that name exists | Pick a unique name | ## 429 Too Many Requests | Code | Cause | Fix | |---|---|---| | `RATE_LIMITED` | Per-second send rate exceeded | Backoff and retry | | `OTP_RATE_LIMITED` | OTP resend before cooldown | Wait 60 seconds | ## 500 Internal Server Error | Code | Cause | Fix | |---|---|---| | `DB_ERROR` | Database query failed | Retry with backoff — usually transient | | `INTERNAL_ERROR` | Catch-all | Retry once; if persistent, contact support | | `PAYMENT_GATEWAY_ERROR` | Zoho upstream returned 5xx | Retry — usually transient | ## 502 Bad Gateway | Code | Cause | Fix | |---|---|---| | `PAYMENT_GATEWAY_ERROR` | Zoho returned an error response | Wait + retry; check Zoho status | ## 503 Service Unavailable | Code | Cause | Fix | |---|---|---| | `DB_UNAVAILABLE` | Database connection issue | Retry — should clear within seconds | ## SMTP errors (in webhook payloads) When a `Bounce` event fires, the `bounce.bouncedRecipients[].diagnosticCode` field carries the SMTP diagnostic from the recipient's MX. Common ones: | Code | Meaning | |---|---| | `550 5.1.1 user unknown` | Recipient doesn't exist (hard bounce) | | `550 5.1.10 No such user` | Same — different MX wording | | `550 5.7.1 spam policy` | Receiver classified as spam (treat as complaint) | | `552 5.2.2 mailbox full` | Soft bounce — retry | | `421 4.7.0 try again later` | Greylisting / load — retry | | `554 5.7.1 sender rejected` | Sender domain blocked by recipient — list cleaning needed | ## Retry strategy Rule of thumb: - **400-class errors:** don't retry. Fix the request. - **402:** don't retry. Recharge first. - **403 + 404:** don't retry. Fix configuration first. - **429:** retry with exponential backoff (start 1s, double up to 60s). - **500 / 502 / 503:** retry up to 3 times with exponential backoff. Idempotency: `/send` is NOT idempotent on retry — duplicate calls with the same body will produce duplicate email sends. Implement your own dedup against your business identifier (charge_id, etc.) before calling `/send`. ## When to contact support - 500-class errors persist > 5 minutes - 402 even though wallet balance shows positive - 403 SENDING_PAUSED with no clear cause in `/reputation` Email support@splashifypro.in or open a ticket from the partner panel. ======================================================================== ## FAQ URL: https://partner-docs.splashifypro.com/knowledge-base/faq ======================================================================== # FAQ ## Account & Billing ### How long does production access take? Most requests are reviewed within 24 business hours. Clean applications (verified domain, real use case, no obvious red flags) are typically approved within an hour. ### Can I have multiple API keys? Yes — generate as many as you need from **Settings → API Keys**. Common pattern: one per environment (staging/production), one per service (web app / cron worker / etc). Revoke individually without affecting the others. ### Is there a free tier? Sandbox accounts get 200 emails/day free. After production access, you pay ₹0.01/email from email #1. There's no monthly minimum or commitment. ### How do I close my account? Email support@splashifypro.in. Wallet balance can either be transferred to a new account or refunded under exceptional circumstances. ## Sending ### Can I send from a Gmail address? No. We refuse to verify public-domain mailboxes (gmail.com, yahoo.com, outlook.com, etc.) — sending bulk through them violates the providers' TOS and would get our IP blocklisted. Use your own domain. ### Can I send to anyone? In **production**, yes. In **sandbox**, only to verified-recipient addresses (email-address identities you've explicitly added). ### What's the maximum recipients per send? 50 per `/send` request. For larger lists, use `/send-bulk` (50 destinations × 50 recipients = 500 max per request) or a campaign-style fan-out. ### What's the maximum email size? 10 MB total (HTML + text + attachments). The MIME-encoded message counts toward this limit. ### Can I attach files? Not via `/send` directly today. `/send-raw` accepts a complete MIME message (base64-encoded) — you build the multipart structure yourself. Attachment-aware send is on the roadmap. ### Are there inline images? With `/send-raw` you control the entire MIME tree, including inline `Content-ID:` references. With `/send` use externally-hosted images (e.g. on a CDN) — most modern email clients strip `cid:` references when forwarded anyway. ## Domain & Identity ### Why do I need to verify a domain? Without verification, anyone could claim to send from your domain through our infrastructure. Verification proves ownership and is required by every modern ESP for the same reason. ### How long does DNS verification take? After publishing the records, typically 5-15 minutes. Some DNS providers cache aggressively (Cloudflare flattening, GoDaddy 24-hour TTL) — call `POST /verify` after publishing and check `status`. ### Can I use a subdomain? Yes — `mail.acme.com` is a valid identity. Common pattern: - `mail.acme.com` for transactional sends - `news.acme.com` for marketing This isolates reputation between mail types. ### What if my DKIM CNAME is flattened by my DNS provider? Cloudflare's CNAME flattening serves the resolved TXT record directly. We accept either CNAME OR TXT for the DKIM check — flattened-but-correct records pass. ## Webhooks ### How fast do webhooks fire after a send? Send + Reject webhooks fire within ~1 second of the API response. Delivery webhooks fire within ~5-30 seconds (the SMTP attempt time). Bounce webhooks fire within seconds (sync hard bounces) or minutes to hours (async DSN bounces). ### What if my webhook endpoint is down? We retry on 5xx + timeout per the [retry schedule](/webhooks/retries) (1min, 5min, 15min). After ~21 minutes total we give up. 4xx responses (auth, schema mismatch) get NO retry — your URL is broken. ### Can I have multiple webhook destinations? Yes — up to 5 per configuration set. Useful for routing engagement events to one URL and deliverability events to another. ### How do I rotate the webhook secret? PATCH the destination with a new `webhook_secret`. The API never echoes the new value back, so save it locally before the next event fires. Brief overlap window is your responsibility — accept both old + new for ~30 seconds during rotation. ## Pricing ### Is the rate marketing-vs-transactional? No — flat ₹0.01 per email regardless of category. Simpler for both sides. ### Are SMS credits separate? Yes — SMS is a separate product with its own billing. This API is email-only. ### Do I get a tax invoice? Yes. Every wallet recharge generates a Zoho Billing invoice with GST (for India-based partners with GSTIN). Downloadable from the partner panel. ## Compliance ### Is this CAN-SPAM compliant? The API supports compliance — auto-injected Unsubscribe headers (RFC 8058 one-click), suppression list, no anonymous sending. But compliance is YOUR responsibility — you must ensure recipients opted in, your unsubscribe link works, and your physical mailing address appears in the body. ### GDPR / DPDP? Same answer — we provide the tools (suppression list for erasure requests, audit logs for data subject access requests). Partner is the data controller; we're the processor. ### What about CASL (Canada)? Same model. Express consent + clear identification + working unsubscribe + 10-day honor window. We honor the platform-level suppression list immediately, but you must process unsubscribe clicks via webhook and update your own user state. ## Migration ### I'm coming from AWS SES — how compatible is the API? Endpoint shapes and response field names map 1:1 in most cases. The webhook event payload shape matches AWS SES SNS event publishing exactly. SDKs feel similar (resource → action). Most migrations are a base-URL swap + an auth header swap. ### From Resend / Postmark / SendGrid? Similar shapes. The biggest difference is the [configuration set + event destination model](/knowledge-base/concepts/configuration-sets) which mirrors AWS SES rather than the per-message metadata model some providers use. If you used SES configuration sets, you're home. ### Can I forward existing webhooks? We don't accept incoming webhooks (we deliver them, we don't receive). For event-source migration, set up a webhook destination on a new config set and migrate sends to use it. ## Support ### Where do I get help? - **Maya AI assistant** — every page has the ✨ button bottom-right - **Search** (Ctrl/⌘+K) — every doc page is indexed - **Email** — support@splashifypro.in - **Partner panel** — built-in support ticket flow ### Is there a status page? Yes — [status.splashifypro.com](https://status.splashifypro.com). We post incidents within ~5 minutes of detection. ### Where's the changelog? Linked from the partner panel footer. Subscribe to the RSS feed to get notified of new endpoints + behaviour changes. ======================================================================== ## Pricing URL: https://partner-docs.splashifypro.com/knowledge-base/pricing ======================================================================== # Pricing We keep it simple — one rate for everyone, sandbox free, prepaid wallet, no monthly minimums. ## Headline | Tier | Rate | |---|---| | **Sandbox (default for new accounts)** | First 200/day **free**, then ₹0.01/email | | **Production** | ₹0.01/email from email #1 | That's it. No marketing-vs-transactional split. No volume tiers that kick in at obscure thresholds. Same price for HTML, plaintext, template, raw MIME, send-bulk — all of it. ## Currency + GST Pricing is in **INR**. Indian partners are billed with 18% GST on top via Zoho Billing. Non-India partners are billed in USD via the same flow with no GST applied. ## Wallet model The Splashify Pro Email API runs on a **prepaid wallet**: 1. Recharge your wallet through the partner panel (`partner.splashifypro.com` → Wallet → Recharge) 2. Each successful send deducts ₹0.01 (or your override rate) from the wallet balance 3. When balance hits zero, `/send` returns `402 INSUFFICIENT_BALANCE` until you recharge Recharges go through Zoho Payments — the platform's payment processor. You get a real Zoho Billing invoice for every recharge, emailed automatically + downloadable from the panel. ## What counts as a billable send A send is billable when: - The API call validates successfully - The recipient is NOT on your suppression list - We attempt SMTP delivery (250 OK or any 4xx/5xx response) NOT billable: - API rejections (`400 INVALID_REQUEST`, `400 FROM_NOT_VERIFIED`) - Suppression-list rejections (`status: rejected` in send response) - Sandbox sends within the daily 200 free quota - Rate-limit rejections (`429`) ## Volume discounts Default is one rate. For partners committing to high steady-state volume, custom rates are negotiable — reach out via the support form on the panel. Admin sets per-partner overrides via `partners.email_marketing_price_override` + `email_transactional_price_override` columns. Once set, those rates take precedence over the default ₹0.01. ## Tracking spend Every send writes a row to your wallet billing log — visible at: ``` partner.splashifypro.com → Wallet → Transaction history ``` Each row shows the recipient, message_id, rate at send-time, and balance before/after. Same data is exposed via API: ```bash curl 'https://api.splashifypro.com/api/v1/wallet/transactions/$PARTNER_ID' \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` ## Recharge minimums Minimum recharge is ₹500. Wallet balance can drop below ₹500 between recharges — we don't gate sends on balance until it hits exactly zero. ## Dedicated IP For partners sending high steady-state volume (typically 10K+/day), a dedicated sending IP isolates your reputation from the shared pool. Request one from the partner panel under **Dedicated IP**. | Item | Rate | |---|---| | **Dedicated IP** | **₹350 / month per IP** | Charged via Zoho Billing on a monthly recurring invoice from the date the IP is assigned. Per-email send pricing (₹0.01) is unchanged — dedicated IP is purely an infrastructure add-on. Cancel anytime; billing stops at the end of the current billing month and the IP is returned to the shared pool. ## Postpaid (legacy) If you're an older partner on a postpaid wallet (`wallet=Post Paid`), sends accrue against your `outstanding` balance up to your `credit_limit`. Reconciled monthly via a Zoho invoice. Newer accounts ship as prepaid by default — postpaid is grandfathered for existing partners. ## What we don't charge for - Verifying identities - Creating configuration sets / templates / event destinations - Webhook deliveries to your endpoint - Suppression list management - Reading stats / quotas / events - API rate limit when you stay under the per-second peak The wallet only deducts on **actual sends**. ## Invoices + GST documents Every recharge generates: - A Zoho Billing invoice (PDF, downloadable from the partner panel) - An automatic email to your account email - Payment record marked PAID in Zoho once the wallet credit lands For India-based partners with a GSTIN, the invoice carries your GSTIN + state code — usable for input-tax-credit reconciliation. ## Refunds - **Wallet balance** is non-refundable as cash. Once recharged, the balance is yours to spend on email sends. - **Failed sends** (4xx/5xx) are not billed in the first place. - **Mistaken duplicate recharges** — contact support, we'll reconcile via the existing duplicate-payment guard tables. - **Account closure** — unused balance can be donated to a future account with the same email or refunded under exceptional circumstances. Contact support. ## Comparison | Provider | Per-email rate | Free tier | |---|---|---| | **Splashify Pro** | ₹0.01 | 200/day in sandbox | | AWS SES (sending alone) | ~$0.0001 (~₹0.008) | 62K/month from EC2 | | Resend | ~$0.0001 (~₹0.008) | 100/day | | Postmark | ~$0.0015 (~₹0.12) | 100/month | | SendGrid | ~$0.0007–$0.001 | 100/day | We come in cheaper than Postmark / SendGrid + competitive with the direct AWS SES rate, with the partner-resold benefits (panel, support, simpler onboarding) layered on top. ======================================================================== ## Webhooks URL: https://partner-docs.splashifypro.com/webhooks ======================================================================== # Webhooks Webhooks let your app react to email lifecycle events as they happen. Send → Delivery → Open → Click → Bounce → Complaint → Reply — every state transition fires an HTTP POST to your URL with a structured JSON payload. ## Two webhook surfaces Pick whichever fits your integration: | Surface | Where you set it | Scope | Best for | |---|---|---|---| | **Account-level webhook** | Settings → Webhook URL on the partner panel | Every event for every send + lifecycle events (production-access approval, account deletion, etc) | Single endpoint that handles everything; simplest setup | | **Per-config-set destinations** | `POST /partner/email/configuration-sets/:id/event-destinations` | Only events scoped to that config set, filtered by `matching_event_types` | Per-customer or per-product routing; advanced filtering | **You can use both at once.** The account-level URL receives every event regardless of config set; the per-config-set destinations receive their filtered subset additionally. Most partners start with just the account-level URL and add per-config-set destinations later when they need to route per-customer. ## Event types fired to your webhook | Event | When | Surface | |---|---|---| | `email.sent` | Recipient MX accepted the message (250 OK) | both | | `email.delivered` | Same as sent — direct-MX collapses these | both | | `email.bounced` | Hard or soft bounce reported via DSN | both | | `email.complained` | Recipient marked as spam (FBL) | both | | `email.opened` | Recipient opened the email (1×1 pixel loaded) | both | | `email.clicked` | Recipient clicked a link in the email | both | | `email.rejected` | Send refused at the API layer (suppression / quota) | both | | `email.replied` | **Recipient replied to the email** | both | | `production_access.approved` | Admin lifted your sandbox cap | account-level only | | `production_access.denied` | Admin denied your sandbox lift request | account-level only | | `account.deletion.requested` | You requested account deletion | account-level only | | `account.deletion.cancelled` | You cancelled the deletion within the 48h window | account-level only | | `rcs_kyc.approved` | Admin approved a customer's [RCS KYC](/api-reference/customers/apply-rcs) | account-level only | | `rcs_kyc.rejected` | Admin rejected a customer's RCS KYC (see `data.rejection_reason`) | account-level only | | `rcs.message.text` | End-user replied with text — see [RCS Incoming](/webhooks/rcs-incoming) | account-level only | | `rcs.message.media` | End-user sent media (image / video / PDF) | account-level only | | `rcs.message.location` | End-user shared a location | account-level only | | `rcs.suggestion.response` | End-user tapped a reply / URL / dial / calendar suggestion | account-level only | | `rcs.status.sent` | Message you sent via [send-rcs](/api-reference/customers/send-rcs) was accepted by the carrier | account-level only | | `rcs.status.delivered` | Delivered to the recipient's device | account-level only | | `rcs.status.read` | Recipient opened the message | account-level only | | `rcs.status.failed` | Carrier-side failure — see `data.rm_event.failure_reason` | account-level only | | `WABA_ONBOARDED` | Customer completed WhatsApp embedded signup — see [WhatsApp Events](/webhooks/whatsapp-events) | account-level only | | WhatsApp `sent` / `delivered` / `read` / `failed` / `deleted` | Outbound message status — raw Meta envelope, forwarded as-is | account-level only | | WhatsApp inbound (`text` / `image` / `video` / `audio` / `document` / `sticker` / `location` / `contacts` / `interactive` / `button` / `order` / `reaction` / `system` / `unknown`) | Customer-initiated messages — raw Meta envelope, forwarded as-is | account-level only | ## How it works ```mermaid sequenceDiagram participant API as Splashify API participant Worker as Webhook dispatcher participant Hook as Your endpoint API->>Worker: Event recorded Worker->>Worker: Find matching destination
(by config_set + event type) Worker->>Hook: POST signed JSON alt 2xx Worker->>Worker: mark delivered else 5xx / timeout Worker->>Worker: schedule retry
(1m, 5m, 15m) else 4xx Worker->>Worker: gave up
(your URL is broken) end ``` 1. You **create a configuration set** (a logical grouping for sends). 2. You **add a webhook event destination** to that config set, with the URL + secret + the event types you want. 3. You **send emails** with `configuration_set_name` referencing that set. 4. Every event for those sends gets POSTed to your URL with an HMAC-signed body. 5. **Retries:** 5xx + timeout → 1min, 5min, 15min. 4xx → no retry (your URL is broken; we don't waste retries). ## Set up a webhook in 3 calls ### 1. Create a config set ```bash curl https://api.splashifypro.com/api/v1/partner/email/configuration-sets \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "production", "suppression_options": "BOUNCE_AND_COMPLAINT" }' ``` Save the `config_set_id` from the response. ### 2. Add a webhook destination ```bash curl https://api.splashifypro.com/api/v1/partner/email/configuration-sets/$CONFIG_SET_ID/event-destinations \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "production-webhook", "destination_type": "WEBHOOK", "webhook_url": "https://yourapp.com/webhooks/splashify", "webhook_secret": "your-shared-secret-32-chars", "matching_event_types": ["send", "delivered", "bounce", "complaint", "open", "click"] }' ``` > **Security:** Store the `webhook_secret` somewhere your endpoint > can read it (env var). The API will never echo it back — to > rotate, PATCH a new value. ### 3. Reference the config set on send ```bash curl https://api.splashifypro.com/api/v1/partner/email/send \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": "hello@yourcompany.com", "to": ["customer@example.com"], "subject": "Welcome", "html_body": "

Hi

", "configuration_set_name": "production" }' ``` Within seconds your endpoint receives a `Send` event, then `Delivery`, etc. ## Payload shape We follow the **AWS SES event-publishing JSON envelope**. Code written against AWS SES SNS subscriptions consumes our webhooks by swapping the auth header verification. ```json { "eventType": "Delivery", "mail": { "timestamp": "2026-05-03T12:34:56Z", "messageId": "550e8400-e29b-41d4-a716-446655440000", "source": "hello@yourcompany.com", "destination": ["customer@example.com"] }, "delivery": { "timestamp": "2026-05-03T12:34:57Z", "recipients": ["customer@example.com"], "smtpResponse": "250 OK" } } ``` Every event has the top-level `eventType` and `mail` fields. The event-specific payload is nested under a key matching the lowercase event type — `delivery`, `bounce`, `complaint`, etc. ## Headers Every POST carries: | Header | Purpose | |---|---| | `Content-Type` | `application/json` | | `User-Agent` | `Splashify-Pro-Webhook/1.0` | | `X-Splashify-Event` | Event type — `Send`, `Delivery`, `Bounce`, ... | | `X-Splashify-Signature` | `sha256=` HMAC-SHA256 of the raw body keyed by your `webhook_secret` | | `X-Splashify-Timestamp` | Unix seconds — protects against replay | | `X-Splashify-Delivery-ID` | UUID per delivery attempt — use for idempotency | ## Quick verify in Node ```js app.post("/webhooks/splashify", express.raw({ type: "application/json" }), (req, res) => { const signature = req.header("x-splashify-signature") || ""; const expected = "sha256=" + crypto .createHmac("sha256", process.env.SPLASHIFY_WEBHOOK_SECRET) .update(req.body) .digest("hex"); if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) { return res.status(401).end(); } const event = JSON.parse(req.body.toString()); // process event... res.status(200).end(); }); ``` ## Next - [**Event Types →**](/webhooks/event-types) — full payload shape per event - [**Verify Signature →**](/webhooks/verify-signature) — implementations in 6 languages - [**Retries & Replays →**](/webhooks/retries) — backoff schedule + manual replay - [**Best Practices →**](/webhooks/best-practices) — idempotency, dedup, security ======================================================================== ## Best Practices URL: https://partner-docs.splashifypro.com/webhooks/best-practices ======================================================================== # Webhook Best Practices ## 1. Verify the signature on every request Constant-time HMAC compare. See [Verify Signature](/webhooks/verify-signature) for per-language code. ## 2. Ack fast, process async Your endpoint has 10 seconds before we time out + retry. The retry DOES NOT mean the first call failed — we have no idea until we get a response. Best pattern: ```js app.post("/webhooks/splashify", express.raw({...}), async (req, res) => { // 1. Verify signature // 2. Push to internal queue await queue.push({ rawBody: req.body.toString(), receivedAt: Date.now() }); // 3. Ack immediately res.status(200).end(); }); ``` Process the queue with retries inside your own infrastructure. Your endpoint becomes a fast Layer-7 doorman. ## 3. Dedup by mail.messageId + eventType Retried deliveries have the same logical content but different `X-Splashify-Delivery-ID` headers. Use the body fields as your dedup key: ```js const key = `${body.mail.messageId}:${body.eventType}`; ``` ## 4. Handle out-of-order delivery The dispatcher fans out one POST per (event, destination) combo in parallel — events for the same email may arrive in any order. A `Bounce` event might land before the `Send` event for the same `messageId` if the bounce was inline. Don't assume `Send → Delivery → Open → Click` arrive in order. Drive your state machine off the absolute event types, not their sequence. ## 5. Monitor your endpoint's health Webhook delivery metrics are available via: ```bash curl 'https://api.splashifypro.com/api/v1/partner/email/events?day_bucket=$(date -u +%Y-%m-%d)' \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` Track: - **Delivery rate** — % of events delivered to your endpoint - **Average latency** — `delivered_at - created_at` - **Failed events** — events with `status=failed` or `gave_up` A sudden drop in delivery rate usually means your endpoint started returning 5xx — check your app logs. ## 6. Don't expose your webhook URL publicly The HMAC signature stops forged events, but exposing the URL still attracts unwanted traffic (probes, fuzzing). Guard at the network edge: - Only allow `POST` (not GET / OPTIONS) - Whitelist our outbound IP range if your firewall supports it. IPs are published at [status.splashifypro.com/ip-ranges](https://status.splashifypro.com) and updated when new sending capacity is added — subscribe to the changelog so you don't miss additions. - Reject any request without `X-Splashify-Signature` ## 7. Use one webhook per concern Don't have one giant webhook handler that branches on `eventType`. Have separate config-set destinations for distinct concerns: ``` config_set "production-engagement" → /webhooks/engagement → matching_event_types: ["open", "click"] config_set "production-deliverability" → /webhooks/deliverability → matching_event_types: ["bounce", "complaint", "reject"] config_set "production-archive" → /webhooks/archive → matching_event_types: ["send", "delivered", "bounce", "complaint", "open", "click", "reject"] ``` Smaller handlers = easier to reason about + scope failures. ## 8. Log raw payloads for the first 30 days Keep raw event bodies + headers in your logs (with the `webhook_secret` redacted). When something looks weird, you have the original payload to diff against your handler's behaviour. ## 9. Replay before going to production Use a tool like [Webhook.site](https://webhook.site) to point test sends at — verify your signature check, JSON parsing, and event-type dispatch logic before pointing your real endpoint at us. ## 10. Have a fallback Webhooks can drop. If your business depends on knowing whether an email delivered, periodically poll `/emails/:message_id` for any message you sent in the last hour that hasn't reached a terminal state via webhook. Reconcile the difference. ======================================================================== ## Event Types URL: https://partner-docs.splashifypro.com/webhooks/event-types ======================================================================== # Event Types The Email API fires nine event types. Subscribe to all of them or just the ones you care about via the `matching_event_types` array on the destination config. | Event | When it fires | Header value | |---|---|---| | [`Send`](#send) | We accept the API call + queue the email | `Send` | | [`Delivery`](#delivery) | Recipient MX returns 250 OK | `Delivery` | | [`Bounce`](#bounce) | Hard or soft bounce | `Bounce` | | [`Complaint`](#complaint) | Recipient marks as spam (FBL report) | `Complaint` | | [`Open`](#open) | Recipient opens the email (pixel loaded) | `Open` | | [`Click`](#click) | Recipient clicks a link in the email | `Click` | | [`Reject`](#reject) | We refused to send (suppression list, etc.) | `Reject` | | [`RenderingFailure`](#renderingfailure) | Template variable substitution failed | `RenderingFailure` | | [`DeliveryDelay`](#deliverydelay) | Soft bounce — will retry | `DeliveryDelay` | Every payload starts with the same envelope: ```json { "eventType": "", "mail": { "timestamp": "ISO8601", "messageId": "uuid", "source": "from-address", "destination": ["to-address"] }, "": { ... } } ``` Below: the event-specific block for each type. ## Send Fires when the API accepts your call. The email is now in the queue — no MX attempt yet. ```json { "eventType": "Send", "mail": { "timestamp": "2026-05-03T12:34:56.123Z", "messageId": "550e8400-e29b-41d4-a716-446655440000", "source": "hello@yourcompany.com", "destination": ["customer@example.com"] }, "send": {} } ``` ## Delivery Fires when the recipient's mailbox provider accepts the email (250 OK). This is the strongest delivery signal — the receiving server has accepted the message for the recipient's mailbox. ```json { "eventType": "Delivery", "mail": { ... }, "delivery": { "timestamp": "2026-05-03T12:34:57.456Z", "recipients": ["customer@example.com"], "smtpResponse": "250 OK" } } ``` ## Bounce Hard bounce (`Permanent`) means the address is dead — recipient is auto-added to your suppression list. Soft bounce (`Transient`) means mailbox-full / server-down / etc. — we retry up to 3 times before giving up. ```json { "eventType": "Bounce", "mail": { ... }, "bounce": { "timestamp": "2026-05-03T12:34:58.789Z", "bounceType": "Permanent", "bounceSubType": "no_such_user", "bouncedRecipients": [ { "emailAddress": "deadbox@example.com", "diagnosticCode": "550 5.1.1 user unknown" } ] } } ``` ## Complaint Recipient marked the email as spam. We received the FBL (feedback- loop) report from their inbox provider and auto-suppressed the address. **Complaints are critical to monitor** — high complaint rates trigger reputation-based sending pauses. ```json { "eventType": "Complaint", "mail": { ... }, "complaint": { "timestamp": "2026-05-03T12:35:10.123Z", "complaintFeedbackType": "abuse", "complainedRecipients": [ { "emailAddress": "customer@example.com" } ] } } ``` ## Open Recipient opened the email — the 1×1 tracking pixel loaded. Privacy- focused mail clients (Apple Mail, etc.) pre-load the pixel proactively, so opens are a directional signal, not an exact one. ```json { "eventType": "Open", "mail": { ... }, "open": { "timestamp": "2026-05-03T12:36:00.000Z", "ipAddress": "203.0.113.42", "userAgent": "Mozilla/5.0 (iPhone; ...)" } } ``` ## Click Recipient clicked a tracked link. We rewrite every `` in the HTML body through a redirect proxy that records the click + 302s to the original URL. Unsubscribe links are NOT tracked. ```json { "eventType": "Click", "mail": { ... }, "click": { "timestamp": "2026-05-03T12:37:00.000Z", "ipAddress": "203.0.113.42", "userAgent": "Mozilla/5.0 (Macintosh; ...)", "link": "https://yourapp.com/dashboard" } } ``` ## Reject We refused to send. Common reasons: recipient on suppression list, sandbox limit reached, sending paused. ```json { "eventType": "Reject", "mail": { ... }, "reject": { "reason": "address on suppression list" } } ``` ## Reply A recipient replied to one of your emails. We catch replies via a unique `Reply-To: reply+@mail.splashifypro.com` we stamp on every outbound (token encodes the original `outbox_id`). When the reply lands at our inbound listener, we parse it, look up which outbound it's responding to, and fire this event. ```json { "eventType": "Reply", "mail": { ... }, "reply": { "outbox_id": "f8c5def1-1234-5678-9abc-def012345678", "original_recipient": "customer@example.com", "from": "Customer Name ", "subject": "Re: Your order has shipped", "message_id": "", "in_reply_to": "", "references": "", "text_body": "Thanks! When can I expect delivery?...", "html_body": "
Thanks! When can I expect...", "received_at": "2026-05-04T09:15:30.123Z" } } ``` `text_body` is truncated to 32 KB and `html_body` to 64 KB — long quoted-thread replies stay inside the webhook envelope without bloating it. Use `in_reply_to` + `references` for thread correlation on your side. **Disabling reply capture.** If you'd rather replies go directly to the address in your `From` header (or your own `Reply-To` if you set one), pass `Reply-To: your-inbox@yourcompany.com` on your send request — when the field is non-empty we won't override it. Replies then route directly to your address and we never see them. > Replies that were already in flight when you change the setting > still arrive at the original `reply+` address until the > recipient updates their thread. ## RenderingFailure Template variable substitution failed — `{{variable}}` referenced in template body wasn't supplied at send time, OR was malformed. ```json { "eventType": "RenderingFailure", "mail": { ... }, "renderingFailure": { "templateName": "welcome", "errorMessage": "missing required variable: first_name" } } ``` ## DeliveryDelay Soft bounce — we'll retry. Fires once on the first retry-eligible soft bounce so you can surface a "delivery delayed" UI without waiting for the final outcome. ```json { "eventType": "DeliveryDelay", "mail": { ... }, "deliveryDelay": { "timestamp": "2026-05-03T12:34:59.000Z", "delayType": "TemporaryFailure", "delayedRecipients": ["customer@example.com"] } } ``` ## Subscribing to a subset Want only bounces and complaints? Set `matching_event_types` on the destination: ```bash curl ... -d '{ "name": "deliverability-alerts", "destination_type": "WEBHOOK", "webhook_url": "https://yourapp.com/webhooks/deliverability", "webhook_secret": "...", "matching_event_types": ["bounce", "complaint"] }' ``` Empty array = subscribe to everything. Specific list = events not on the list are skipped. ## Multiple destinations per config set You can attach multiple webhook destinations to a single config set — one for engagement events (open / click), one for deliverability alerts (bounce / complaint), one for archival (everything). Each destination delivers + retries independently. ======================================================================== ## RCS Events URL: https://partner-docs.splashifypro.com/webhooks/rcs-events ======================================================================== # RCS Events When a partner customer applies for RCS via [`POST /partner/customers/{customer_id}/rcs/apply`](/api-reference/customers/apply-rcs), the submission queues for admin review. The moment admin approves or rejects it, we POST an event to the **account-level webhook URL** you configured under Settings → Webhook URL on the partner panel. These events do **not** flow through the per-config-set destinations — that surface is email-only. RCS events use the same channel as `production_access.approved` and `account.deletion.requested`. ## Event types | Event | When | |---|---| | `rcs_kyc.approved` | Admin approved the customer's RCS KYC. RCS sender registration follows. | | `rcs_kyc.rejected` | Admin rejected the submission. `data.rejection_reason` explains why. Re-call `apply` with a corrected payload to resubmit. | ## Envelope Account-level webhooks share this envelope (different from the email events' SES-shaped envelope — same headers, different body): ```json { "event_type": "rcs_kyc.approved", "partner_id": "11111111-2222-3333-4444-555555555555", "occurred_at": "2026-05-21T07:04:11.482Z", "data": { /* event-specific, see below */ } } ``` ## `rcs_kyc.approved` payload ```json { "event_type": "rcs_kyc.approved", "partner_id": "11111111-2222-3333-4444-555555555555", "occurred_at": "2026-05-21T07:04:11.482Z", "data": { "customer_id": "8f3b2a1e-49d8-4c2d-9e1a-b7e6d5f8a3c2", "customer_name": "Acme Corporation", "customer_email": "contact@acme.com", "customer_phone": "+919876543210", "status": "approved", "rejection_reason": "", "reviewed_by": "ops@evolvepro.tech", "reviewed_at": "2026-05-21T07:04:11.482Z", "submitted_at": "2026-05-20T10:18:42.000Z" } } ``` ## `rcs_kyc.rejected` payload ```json { "event_type": "rcs_kyc.rejected", "partner_id": "11111111-2222-3333-4444-555555555555", "occurred_at": "2026-05-21T07:04:11.482Z", "data": { "customer_id": "8f3b2a1e-49d8-4c2d-9e1a-b7e6d5f8a3c2", "customer_name": "Acme Corporation", "customer_email": "contact@acme.com", "customer_phone": "+919876543210", "status": "rejected", "rejection_reason": "Bot logo is 512×512; we need 224×224 JPEG ≤ 50 KB.", "reviewed_by": "ops@evolvepro.tech", "reviewed_at": "2026-05-21T07:04:11.482Z", "submitted_at": "2026-05-20T10:18:42.000Z" } } ``` ## Headers Same headers as all account-level webhooks: | Header | Value / purpose | |---|---| | `Content-Type` | `application/json` | | `User-Agent` | `Splashify-Pro-Webhook/1.0` | | `X-Splashify-Event` | `rcs_kyc.approved` or `rcs_kyc.rejected` | | `X-Splashify-Delivery-ID` | UUID per delivery attempt — use for idempotency | | `X-Splashify-Timestamp` | Unix seconds — replay protection | > Signature verification (`X-Splashify-Signature`) is only emitted by > the email-events surface today. RCS events arrive unsigned over > HTTPS; whitelist the source by domain if you need stricter > authentication. ## Handler example (Node) ```js app.post("/webhooks/splashify", express.json(), (req, res) => { const event = req.headers["x-splashify-event"]; const { partner_id, occurred_at, data } = req.body; switch (event) { case "rcs_kyc.approved": await db.customers.update(data.customer_id, { rcs_status: "approved", rcs_approved_at: data.reviewed_at, }); break; case "rcs_kyc.rejected": await db.customers.update(data.customer_id, { rcs_status: "rejected", rcs_rejection_reason: data.rejection_reason, }); await notifyTeam(`RCS rejected for ${data.customer_email}: ${data.rejection_reason}`); break; } res.status(200).end(); }); ``` ## Delivery semantics - **Fire-and-forget.** Admin's approve/reject action commits regardless of whether your webhook returns 2xx. A failed delivery doesn't block the status flip. - **No retries on this surface.** Account-level webhooks log 4xx/5xx responses but do not retry. Keep your endpoint healthy, or poll [`GET /partner/customers/{customer_id}/rcs/status`](/api-reference/customers/rcs-status) as a fallback. - **10 s timeout.** If your endpoint takes longer to respond, the request is cancelled and logged as a delivery failure. - **Order is not guaranteed.** Don't assume `rcs_kyc.approved` arrives before `rcs_kyc.rejected` for the same customer in a re-review scenario. Always trust `data.status` and `occurred_at`. ## Idempotency Same delivery ID retried (e.g. by a manual replay tool — coming) will carry the same `X-Splashify-Delivery-ID`. De-dupe on it. For the underlying event itself, the tuple `(partner_id, data.customer_id, data.status, occurred_at)` is unique — two reviews of the same submission produce two events with different `occurred_at`. ======================================================================== ## RCS Incoming Events URL: https://partner-docs.splashifypro.com/webhooks/rcs-incoming ======================================================================== # RCS Incoming Events Every Route Mobile callback for a customer that is **RCS-Active** under your partner account is forwarded to your account-level **Webhook URL** (set under Settings → Webhook URL on the partner panel). There are two sources of these callbacks: - **Inbound messages** — the end-user sent a text, media file, location, or tapped a suggestion. - **Status updates** — a message you sent via [`POST …/customers/{customer_id}/rcs/send`](/api-reference/customers/send-rcs) changed state (sent → delivered → read, or failed). Inbound events are routed to your partner account based on the customer the message was sent to. You only ever see events for customers under your account — there is no cross-tenant leakage. ## Event types | Event | When | RM event_type | |---|---|---| | `rcs.message.text` | End-user replied with text | `text_message` | | `rcs.message.media` | End-user sent image / video / PDF / other file | `media_message` | | `rcs.message.location` | End-user shared a location | `location_message` | | `rcs.suggestion.response` | End-user tapped a suggestion (reply / URL / dial / calendar / location) | `response` | | `rcs.status.sent` | Message you sent was accepted by the recipient's carrier | `status` + `SENT` | | `rcs.status.delivered` | Message was delivered to the recipient's device | `status` + `DELIVERED` | | `rcs.status.read` | Recipient opened the message | `status` + `READ` | | `rcs.status.failed` | Message could not be delivered | `status` + `FAILED` | | `rcs.status.unknown` | RM emitted a message_status we don't recognise — surface verbatim | `status` + other | ## Envelope Same envelope as the rest of the account-level webhooks (production access, account deletion, RCS KYC, etc.): ```json { "event_type": "rcs.message.text", "partner_id": "11111111-2222-3333-4444-555555555555", "occurred_at": "2026-05-20T08:26:32.082Z", "data": { "customer_id": "8f3b2a1e-49d8-4c2d-9e1a-b7e6d5f8a3c2", "bot_name": "SplashifyPro_promo", "rm_event": { /* unmodified Route Mobile payload — see below */ } } } ``` `data.rm_event` is the **raw** body Route Mobile delivered to us. The normalised fields we extract for routing live alongside (`customer_id`, `bot_name`), but everything else is in `rm_event` for partners who want to read RM's payload directly without us getting in the way. ## Inbound payloads ### `rcs.message.text` ```json { "event_type": "rcs.message.text", "partner_id": "...", "occurred_at": "2026-05-20T08:26:32.082Z", "data": { "customer_id": "...", "bot_name": "SplashifyPro_promo", "rm_event": { "bot_name": "SplashifyPro_promo", "event_type": "text_message", "request_id": "MxbipjUYLMRD27ZaJRMaSKgA2", "session_id": "ecfd6ac3-3c9c-4dfc-97df-9e759c2a93d5", "text_message": "Hi", "timestamp": "2026-05-20T08:26:32.080697Z", "user_contact": "+919876543210", "username": "demo" } } } ``` ### `rcs.message.media` `rm_event.media_type` is one of `image`, `video`, `application` (documents). `media_uri` is a temporary Google RCS blob URL — fetch within minutes if you want to persist the file. ```json { "event_type": "rcs.message.media", "partner_id": "...", "occurred_at": "2026-05-20T08:27:44.448Z", "data": { "customer_id": "...", "bot_name": "SplashifyPro_promo", "rm_event": { "bot_name": "SplashifyPro_promo", "event_type": "media_message", "media_name": "5023611655770832826.jpg", "media_size": "67541", "media_type": "image", "media_uri": "https://rcs-copper-ap.googleapis.com/blob/...", "request_id": "MxWpHXTmckS3GRVTVa6VMydg2", "session_id": "ecfd6ac3-3c9c-4dfc-97df-9e759c2a93d5", "timestamp": "2026-05-20T08:27:44.445575Z", "user_contact": "+919876543210", "username": "demo" } } } ``` ### `rcs.message.location` ```json { "event_type": "rcs.message.location", "partner_id": "...", "occurred_at": "2026-05-20T08:28:25.144Z", "data": { "customer_id": "...", "bot_name": "SplashifyPro_promo", "rm_event": { "bot_name": "SplashifyPro_promo", "event_type": "location_message", "latitude": "12.972652", "longitude": "77.7110861", "request_id": "Mx93B8zFOdSzCySLkCcHxxIQ2", "session_id": "ecfd6ac3-3c9c-4dfc-97df-9e759c2a93d5", "timestamp": "2026-05-20T08:28:25.142098Z", "user_contact": "+919876543210", "username": "demo" } } } ``` ### `rcs.suggestion.response` End-user tapped one of the suggestions you attached to a previous message. `rm_event.suggestion_type` is `reply`, `action` (URL or dial), or `view_location`. The `response_postback` matches the `postback` you sent with the suggestion — use it as a stable correlation key. ```json { "event_type": "rcs.suggestion.response", "partner_id": "...", "occurred_at": "2026-05-20T10:49:08.724Z", "data": { "customer_id": "...", "bot_name": "SplashifyPro_promo", "rm_event": { "bot_name": "SplashifyPro_promo", "event_type": "response", "request_id": "MxU4UwnudnTuuexqE6ZSYeDQ2", "response_postback": "track", "response_text": "Track order", "session_id": "ecfd6ac3-3c9c-4dfc-97df-9e759c2a93d5", "suggestion_type": "action", "timestamp": "2026-05-20T10:49:08.720679Z", "user_contact": "+919876543210", "username": "demo" } } } ``` ## Status payloads Status events reference the `message_id` you got back from the send call. Use it as your join key against your own outbound table. ### `rcs.status.sent` / `delivered` / `read` ```json { "event_type": "rcs.status.delivered", "partner_id": "...", "occurred_at": "2026-05-25T12:58:13.683Z", "data": { "customer_id": "...", "bot_name": "SplashifyPro_promo", "rm_event": { "bot_name": "SplashifyPro_promo", "event_type": "status", "message_id": "61c11dfe-5839-11f1-a96f-0a58a9feac021", "message_status": "DELIVERED", "timestamp": "2026-05-25T12:58:13.683308Z", "user_contact": "+917014311953", "username": "EvolveRCS", "extra": "lifecycle_trial_expired" } } } ``` `sent` and `read` payloads are identical apart from the `event_type` and the `rm_event.message_status` field. #### About `rm_event.extra` — your passthrough field Whatever string you passed in the `extra` field of the original [Send RCS](/api-reference/customers/send-rcs) call comes back here verbatim on every status callback. We don't interpret, modify, or truncate it. Common uses: - **Idempotency key** — set `extra` to a UUID per logical send; if network retries cause the same logical message to send twice, you see the same `extra` on both delivery callbacks and dedupe on your side. - **Lifecycle tag** — set `extra` to your internal campaign / segment name (e.g. `lifecycle_trial_expired`, `cart_abandon_24h`). Pivot delivery rates by tag without a separate join table. - **Trace ID** — set `extra` to the trace ID from your job system, so the callback lands in the same trace as the originating send. If you don't send `extra`, the field is omitted from the callback. ### `rcs.status.failed` ```json { "event_type": "rcs.status.failed", "partner_id": "...", "occurred_at": "2026-05-20T08:12:12.903Z", "data": { "customer_id": "...", "bot_name": "SplashifyPro_promo", "rm_event": { "bot_name": "SplashifyPro_promo", "event_type": "status", "failure_reason": "user is not in a conversation and provided message template is not approved", "message_id": "faea56dc-2b1a-11f0-a4b9-0a58a9feac02", "message_status": "FAILED", "timestamp": "2026-05-20T08:12:12.900Z", "user_contact": "+919876543210", "username": "demo", "extra": "cart_abandon_24h" } } } ``` `rm_event.extra` is also echoed on failure callbacks, so your idempotency / lifecycle / trace tagging works end-to-end even when the send doesn't reach the recipient. `failure_reason` is the carrier-side reason. Common values: - *"user is not in a conversation and provided message template is not approved"* — recipient hasn't opted in / your bot is sandboxed. - *"user device does not support RCS"* — fall back to SMS / WhatsApp. - *"invalid recipient number"* — your `phone_no` was rejected. ## Headers Same headers as every account-level webhook: | Header | Value / purpose | |---|---| | `Content-Type` | `application/json` | | `User-Agent` | `Splashify-Pro-Webhook/1.0` | | `X-Splashify-Event` | `rcs.message.text` / `rcs.status.delivered` / ... | | `X-Splashify-Delivery-ID` | UUID per delivery attempt — idempotency key | | `X-Splashify-Timestamp` | Unix seconds — replay protection | > Signature verification is not emitted on this surface today. Pin the > source by hostname (`api.splashifypro.com`) if you need stricter > auth. ## Node handler example ```js app.post("/webhooks/splashify", express.json(), async (req, res) => { const event = req.headers["x-splashify-event"]; const { data } = req.body; switch (event) { case "rcs.message.text": await store.inbound({ customer_id: data.customer_id, from: data.rm_event.user_contact, text: data.rm_event.text_message, session_id: data.rm_event.session_id, }); break; case "rcs.suggestion.response": // Use the postback you sent originally to route the reply await store.handleSuggestion(data.rm_event.response_postback, data); break; case "rcs.status.sent": case "rcs.status.delivered": case "rcs.status.read": await store.updateOutbound(data.rm_event.message_id, event.split(".").pop()); break; case "rcs.status.failed": await alerts.failedRCS(data.rm_event.message_id, data.rm_event.failure_reason); break; } res.status(200).end(); }); ``` ## Delivery semantics - **Fire-and-forget.** Route Mobile gets a 200 OK from us regardless of whether your endpoint succeeds. A failed delivery doesn't queue a retry — RM doesn't know about your endpoint. - **No retries on this surface.** 4xx/5xx from your endpoint is logged and dropped. Keep the endpoint healthy. - **10 s timeout.** If your endpoint doesn't respond in 10 s the request is cancelled. Acknowledge fast, process async. - **Order is not guaranteed.** `sent`, `delivered`, and `read` for the same `message_id` can arrive out of order. Always trust the latest `rm_event.timestamp`. ## Idempotency `X-Splashify-Delivery-ID` is unique per HTTP delivery attempt. For the underlying event, use: - inbound messages: `rm_event.request_id` (RM's own dedupe key) - status events: `(rm_event.message_id, event_type)` ======================================================================== ## Retries & Replays URL: https://partner-docs.splashifypro.com/webhooks/retries ======================================================================== # Retries & Replays The webhook dispatcher retries on transient failures and gives up on permanent ones. This page covers the exact behaviour so your endpoint can plan for it. ## Retry schedule | Attempt | Backoff | When | |---|---|---| | 1 | — | Immediate (within seconds of the event) | | 2 | 1 minute | After attempt 1 fails | | 3 | 5 minutes | After attempt 2 fails | | 4 | 15 minutes | After attempt 3 fails | | Final | give up | After attempt 4 fails — no further retries | Total retry window: ~21 minutes. After that the event is marked `gave_up` in our audit log and no further attempts are made. ## What triggers a retry | Response | Retry? | Reason | |---|---|---| | 2xx (200–299) | No | Delivered | | 3xx | Yes | We don't follow redirects — set up your URL to respond directly | | 4xx (except 408, 429) | **No** | Your endpoint is misconfigured / rejecting; retries waste both sides' budget | | 408 (timeout) | Yes | Treated as transient | | 429 (rate limit) | Yes | Backoff respects `Retry-After` header up to 1 hour | | 5xx | Yes | Transient — recipient claimed the event but couldn't process | | Network timeout | Yes | 10-second timeout per request | ## Why 4xx doesn't retry Most 4xx responses indicate your endpoint is broken in a way time won't fix: - 401/403 — wrong shared secret - 404 — endpoint doesn't exist - 405 — wrong HTTP method - 422 — payload schema mismatch If your endpoint *needs* time to recover (e.g. you just deployed + the route 404s for 30 seconds), respond with `503` instead of `404` during the deploy window. We'll retry. ## Idempotency Every webhook attempt carries a unique `X-Splashify-Delivery-ID` header. Even retries of the same event have a fresh delivery ID. To dedup at your end, key off `mail.messageId` + `eventType`: ```js const key = `${body.mail.messageId}:${body.eventType}`; const inserted = await db.events.insertOne({ _id: key, ...body, receivedAt: new Date(), }, { ignoreDuplicates: true }); if (!inserted.insertedCount) { return res.status(200).end(); // already processed, ack } // ... process ``` The combination is unique per event — even if we retry due to your 500 response, the second delivery has the same `mail.messageId` + `eventType` and your insert dedups it. ## Manual replay For events that gave up (your endpoint was down for >21 minutes), contact support to replay them. We retain the full event log for 30 days and can re-fan-out a specific window. Self-serve replay is on the roadmap — `POST /partner/email/events/replay` with a time range + optional event-type filter. ## Inspecting webhook delivery state To see whether an event was delivered to a destination: ```bash curl 'https://api.splashifypro.com/api/v1/partner/email/events?day_bucket=2026-05-03' \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" ``` Response includes per-destination delivery status (delivered, failed, gave_up) so you can see which webhooks landed. ## Common reasons for repeated failures - **Endpoint times out > 10 seconds.** Your handler should ack ASAP (write to a queue, return 200) and process async. - **Endpoint behind Cloudflare with bot protection.** Bot challenges return 403 to our requests — whitelist our user-agent (`Splashify-Pro-Webhook/1.0`) or our outbound IP range. - **HTTPS cert expired.** We don't disable cert verification. Renew + monitor. - **Wrong secret.** If you rotated the secret in the panel but didn't roll it on your endpoint, every signature check returns 401. ======================================================================== ## Open & click tracking URL: https://partner-docs.splashifypro.com/webhooks/tracking ======================================================================== # Open & click tracking Every email sent through Splashify Pro can carry two kinds of recipient-side instrumentation: | Tracker | Mechanism | Generates | |---|---|---| | **Open** | 1×1 transparent GIF embedded just before `` | `email.opened` event | | **Click** | `` rewriter routing every absolute URL through a redirect proxy | `email.clicked` event | Both are **on by default** on every send (matches AWS SES + Resend + Postmark default). Turn either off when: - You're sending **transactional** mail where engagement metrics aren't useful (password resets, OTP codes, receipts) and the rewritten URLs trip URL-pattern recognition in mail clients. - The recipient population is **privacy-sensitive** (healthcare, legal, financial) and tracking pixels are off-limits per your agreement with them. - You ship **plaintext-only** email — opens require an HTML body to host the pixel; if there's no HTML you'll never see opens regardless of the flag. ## Set defaults per configuration set Tracking flags live on each configuration set, so you can have one config set for marketing (tracked) and another for transactional (untracked): ```bash # Create a config set with tracking ON (default — flags optional) curl https://api.splashifypro.com/api/v1/partner/email/configuration-sets \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "marketing", "track_opens": true, "track_clicks": true }' # Create a config set with tracking OFF — privacy / transactional flow curl https://api.splashifypro.com/api/v1/partner/email/configuration-sets \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "password-reset", "track_opens": false, "track_clicks": false }' ``` Update an existing config set: ```bash curl -X PATCH \ https://api.splashifypro.com/api/v1/partner/email/configuration-sets/$CONFIG_SET_ID \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "track_opens": false }' ``` ## Reference the config set on send ```bash curl https://api.splashifypro.com/api/v1/partner/email/send \ -H "Authorization: Bearer $SPLASHIFY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": "noreply@yourcompany.com", "to": ["customer@example.com"], "subject": "Your password reset code", "text_body": "Code: 123456", "html_body": "

Code: 123456

", "configuration_set_name": "password-reset" }' ``` The pixel + click rewriter are **skipped** for this send because the config set has both flags off. Recipient sees a clean HTML message without the `/t/o/` pixel or `/t/c/` redirected hrefs. ## Send without a configuration set When you don't pass `configuration_set_name`, both flags default to **true**. To send untracked without setting up a config set, the simplest path is a one-off config set with both off — there's no per-send override flag yet. ## Use your own tracking instead If you have your own analytics (e.g. Plausible, Fathom, an internal warehouse), turn off Splashify tracking on the config set + add your own UTM params or pixel directly inside your HTML body. Splashify won't add a second pixel + won't rewrite your `
` — your links arrive at the recipient verbatim with whatever tracking you embedded. ```html Log in ``` ## What this affects | Surface | With tracking ON | With tracking OFF | |---|---|---| | Recipient HTML | `` rewritten through `/t/c/`; pixel injected before `` | Verbatim HTML, no rewrites | | `email.opened` event | Fires on pixel load | Never fires (no pixel) | | `email.clicked` event | Fires on link click | Never fires (no rewrite) | | `email.sent` / `email.delivered` / `email.bounced` | Fires regardless | Fires regardless | | Bounce / complaint webhooks | Fires regardless | Fires regardless | | Reputation calculation | Unaffected — uses bounce + complaint, not opens / clicks | Unaffected | ## Privacy & compliance notes - **GDPR / DPDP**: tracking pixels can be considered personal data processing. If you operate in jurisdictions requiring explicit consent for tracking, default new senders to a config set with tracking off + opt them in only after consent. - **Apple Mail Privacy Protection (MPP)**: pre-loads pixels on Apple devices, so opens from those clients are inflated. Many partners drop opens entirely + use clicks as the engagement signal. - **Pixel blocking**: Gmail's image proxy serves the pixel from Google's edge — you still get opens but with Google's IP + user agent. Outlook desktop blocks images by default → no open event unless the recipient clicks "show images". ## See also - [Event types →](/webhooks/event-types) — `email.opened` + `email.clicked` + `email.replied` payload details - [Configuration sets →](/api-reference/configuration-sets) — full CRUD reference - [Anti-spam best practices →](/legal/anti-spam) — when tracking helps and when it doesn't ======================================================================== ## Verify Signature URL: https://partner-docs.splashifypro.com/webhooks/verify-signature ======================================================================== # Verify Signature Every webhook POST carries an `X-Splashify-Signature` header — the HMAC-SHA256 of the **raw request body** keyed by the `webhook_secret` you configured on the destination, hex-encoded with a `sha256=` prefix. > **Verify in constant time.** Standard string equality is > vulnerable to timing attacks. Use your language's > `hmac.compare_digest` / `crypto.timingSafeEqual` / equivalent. ## Node.js ```js const app = express(); // IMPORTANT: read the body as raw bytes, not as parsed JSON, so the // HMAC computation matches what we signed. app.post( "/webhooks/splashify", express.raw({ type: "application/json" }), (req, res) => { const sig = req.header("x-splashify-signature") || ""; const expected = "sha256=" + crypto .createHmac("sha256", process.env.SPLASHIFY_WEBHOOK_SECRET) .update(req.body) .digest("hex"); if (sig.length !== expected.length || !crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) { return res.status(401).end(); } const event = JSON.parse(req.body.toString()); handle(event); res.status(200).end(); }, ); ``` ## Python (Flask) ```python from flask import Flask, request app = Flask(__name__) SECRET = os.environ["SPLASHIFY_WEBHOOK_SECRET"].encode() @app.post("/webhooks/splashify") def webhook(): sig = request.headers.get("X-Splashify-Signature", "") expected = "sha256=" + hmac.new(SECRET, request.data, hashlib.sha256).hexdigest() if not hmac.compare_digest(sig, expected): return ("", 401) event = request.get_json() handle(event) return ("", 200) ``` ## PHP ```php Result<(), StatusCode> { let sig = headers.get("X-Splashify-Signature") .and_then(|h| h.to_str().ok()).unwrap_or(""); let mut mac = Hmac::::new_from_slice(secret.as_bytes()).unwrap(); mac.update(&body); let expected = format!("sha256={}", hex::encode(mac.finalize().into_bytes())); if !sig.as_bytes().ct_eq(expected.as_bytes()).into() { return Err(StatusCode::UNAUTHORIZED); } // ... handle Ok(()) } ``` ## Common pitfalls - **Don't parse the body before computing HMAC.** Most frameworks parse + serialize JSON, which can change byte-for-byte (whitespace, field order). Always sign the raw bytes you received. - **Constant-time compare.** `==` on strings leaks timing information. Always use the language's secure-compare function. - **Rotation.** When you rotate `webhook_secret` via PATCH, BOTH the old and new value should accept incoming events for ~30 seconds while in-flight requests drain. Implement by storing the previous secret + falling back to it on signature mismatch for a brief window. - **Reject early.** Verify the signature BEFORE any expensive work (DB queries, external calls). Saves resources on forged requests. ## Test fixtures Want to verify your implementation handles a known-good payload? Use this: ``` Body: {"eventType":"Send","mail":{"timestamp":"2026-05-03T12:00:00Z","messageId":"abc","source":"a@b.com","destination":["c@d.com"]},"send":{}} Secret: test-secret Signature: sha256=2bd8e57e9f5b2e8d2f8c4d1c9a1b9c3a3a4f5d6e7c8b9a0d1e2f3a4b5c6d7e8f ``` If your code computes the same hex string, you're verified. ======================================================================== ## WhatsApp Events URL: https://partner-docs.splashifypro.com/webhooks/whatsapp-events ======================================================================== # WhatsApp Events Every Cloud API webhook for a customer that's been onboarded through [TP-Signup](/api-reference/whatsapp/tp-signup) is forwarded to your account-level **Webhook URL** (set under Settings → Webhook URL on the partner panel). Three categories: - **Onboarding** — `whatsapp.waba_onboarded` fires once when the customer completes embedded signup. Internal tokens stripped. - **Status updates** — `sent` / `delivered` / `read` / `failed` / `deleted` for every outbound message you send. - **Inbound messages** — text, media, location, interactive responses, and the other Cloud API message types. WABA → customer mapping is done server-side at TP-Signup time. You only ever see events for customers under your account; cross-tenant leakage is impossible. ## Headers Every POST carries: | Header | Value | |---|---| | `Content-Type` | `application/json` | | `X-Webhook-Source` | `splashifypro` | | `X-Webhook-Type` | `meta_raw` for status & inbound; absent for onboarding events | ## 1. `whatsapp.waba_onboarded` Fires once when the upstream onboarding completes for a customer. The `isv_name_token` field that Meta includes in this event is **stripped** before delivery — do not expect or rely on it. ```json { "event": "WABA_ONBOARDED", "source": "splashifypro", "waba_id": "123456789012345", "phone_number_id": "623925589026353", "phone_number": "+919999999999" } ``` Once you receive this, the customer's phone numbers are immediately available via the [Phone Numbers](/api-reference/whatsapp/phone-numbers) endpoint. ## 2. Status updates (`sent` / `delivered` / `read` / `failed` / `deleted`) These follow the standard WhatsApp Cloud API envelope. The full raw Meta payload is forwarded as-is. Status events all share the same top-level shape; the `statuses[].status` field tells you which it is. ### `sent` Fires when the upstream accepts your outbound for delivery. The `conversation.origin.type` field reflects the session origin (`marketing` / `utility` / `authentication` / `service` / `referral_conversion`). ```json { "object": "whatsapp_business_account", "entry": [{ "id": "3130247400631305", "changes": [{ "value": { "messaging_product": "whatsapp", "metadata": { "display_phone_number": "91XXXXXXXXXX", "phone_number_id": "623925589026353" }, "statuses": [{ "id": "wamid.HBgM...", "status": "sent", "timestamp": "1655287862", "recipient_id": "91XXXXXXXXXX", "conversation": { "id": "92d5c04d20c643078be036db3ac05026", "expiration_timestamp": "1655372820", "origin": { "type": "marketing" } }, "pricing": { "billable": true, "pricing_model": "CBP", "category": "marketing" } }] }, "field": "messages" }] }] } ``` ### `delivered` / `read` Same envelope, with `statuses[].status` set to `delivered` or `read`. `pricing` may be absent on basic `read` callbacks. Use these to update your message-tracker UI; the *billing* trigger on our side is controlled by the per-partner deduction-trigger toggle (admin-side). ### `failed` ```json { "object": "whatsapp_business_account", "entry": [{ "id": "1568505090181585", "changes": [{ "value": { "messaging_product": "whatsapp", "metadata": { "...": "..." }, "statuses": [{ "id": "wamid.HBgM...", "status": "failed", "timestamp": "1655287620", "recipient_id": "91XXXXXXXXXX", "errors": [{ "code": 131047, "title": "Message failed to send because more than 24 hours have passed since the customer last replied to this number", "href": "https://developers.facebook.com/docs/whatsapp/cloud-api/support/error-codes/" }] }] }, "field": "messages" }] }] } ``` `errors[]` carries the Meta error code; cross-reference the official [error-codes table](https://developers.facebook.com/docs/whatsapp/cloud-api/support/error-codes/). ### `deleted` Light envelope (no `conversation` / `pricing`): ```json { "object": "whatsapp_business_account", "entry": [{ "id": "3130247400631305", "changes": [{ "value": { "messaging_product": "whatsapp", "metadata": { "...": "..." }, "statuses": [{ "id": "wamid.HBgM...", "status": "deleted", "timestamp": "1655287862", "recipient_id": "91XXXXXXXXXX" }] }, "field": "messages" }] }] } ``` ## 3. Inbound messages Forwarded verbatim from Meta. `entry[].changes[].value.messages[].type` tells you the kind: | `type` | Message contains | |---|---| | `text` | `text.body` — plain text reply | | `reaction` | `reaction.message_id` + `reaction.emoji` (≤ 30 days old) | | `image` / `video` / `audio` / `document` / `sticker` | `.id` (media handle) + `mime_type` + `sha256` | | `audio` with `voice: true` | Voice note (`mime_type: "audio/ogg; codecs=opus"`) | | `location` | `location.latitude` + `location.longitude` | | `contacts` | `contacts[]` with `name` + `phones` | | `interactive` (`button_reply` / `list_reply`) | The button or list item the user tapped | | `button` | Quick-reply payload from a template message | | `order` | Catalog product order; carries `order.product_items[]` | | `system` | Service notifications (e.g. `user_changed_number`) | | `unknown` | Unsupported type with an `errors[]` description | Example — text message: ```json { "object": "whatsapp_business_account", "entry": [{ "id": "2427770783922677", "changes": [{ "value": { "messaging_product": "whatsapp", "metadata": { "display_phone_number": "91XXXXXXXXXX", "phone_number_id": "586727755839684" }, "contacts": [{ "profile": { "name": "Pinnacle" }, "wa_id": "91XXXXXXXXXX" }], "messages": [{ "from": "91XXXXXXXXXX", "id": "wamid.HBgM...", "timestamp": "1655526425", "type": "text", "text": { "body": "Test message" } }] }, "field": "messages" }] }] } ``` Example — interactive list reply with context: ```json { "messages": [{ "context": { "from": "91XXXXXXXXXX", "id": "wamid.HBgM..." }, "from": "91XXXXXXXXXX", "id": "wamid.HBgM...", "timestamp": "1655538521", "type": "interactive", "interactive": { "type": "list_reply", "list_reply": { "id": "id_1", "title": "one" } } }] } ``` Example — click-to-WhatsApp ad referral: ```json { "messages": [{ "referral": { "source_url": "AD_OR_POST_FB_URL", "source_id": "ADID", "source_type": "ad", "headline": "AD_TITLE", "body": "AD_DESCRIPTION", "media_type": "image", "image_url": "RAW_IMAGE_URL", "thumbnail_url":"RAW_THUMBNAIL_URL" }, "from": "SENDER_PHONE", "id": "wamid.ID", "timestamp": "TIMESTAMP", "type": "text", "text": { "body": "BODY" } }] } ``` ## Delivery semantics - **Fire-and-forget.** Meta gets a 200 OK from us regardless of what your endpoint returns. No retries on this surface — keep your endpoint healthy or rely on idempotent processing keyed on `statuses[].id` / `messages[].id`. - **10 s timeout.** Acknowledge fast, process async. - **Order is not guaranteed.** Multiple status callbacks for the same `wamid` can arrive out of order. Always trust the latest `statuses[].timestamp`. - **Bandwidth.** Media isn't included inline — you receive the Meta media `id` and `sha256`. Use the WhatsApp media endpoints to download. ## Node handler example ```js app.post("/webhooks/splashify", express.json(), async (req, res) => { const body = req.body; if (body.event === "WABA_ONBOARDED") { await db.customers.markOnboarded(body.waba_id, body.phone_number_id); return res.status(200).end(); } for (const entry of body.entry ?? []) { for (const ch of entry.changes ?? []) { const v = ch.value ?? {}; for (const s of v.statuses ?? []) { await db.outbound.updateStatus(s.id, s.status, s); } for (const m of v.messages ?? []) { await db.inbound.persist({ from: m.from, phone_number_id: v.metadata?.phone_number_id, ...m, }); } } } res.status(200).end(); }); ``` ======================================================================== ## Legal & Compliance URL: https://partner-docs.splashifypro.com/legal ======================================================================== # Legal & Compliance This section is the **canonical source** for the legal terms that apply when you use the Splashify Pro Email API. By creating an account or sending a single API request, you agree to everything documented here. ## Quick map | Document | What it covers | |---|---| | [Acceptable Use Policy](/legal/acceptable-use-policy) | What you can and cannot send. Banned content. Banned recipient practices. | | [Auto-Action System](/legal/auto-actions) | Automated thresholds that throttle, pause, or terminate accounts — how they fire, how to recover. | | [Anti-Spam Policy](/legal/anti-spam) | Consent requirements, list-acquisition rules, unsubscribe handling. | | [Data Processing Agreement](/legal/data-processing-agreement) | Controller/processor terms under the DPDP Act 2023 and GDPR. | | [Service Level Agreement](/legal/service-level-agreement) | Uptime targets, support response, service credits. | | [API Terms of Service](/legal/terms-of-service) | The contract between you and us. | | [Privacy Policy](/legal/privacy-policy) | What we collect, why, how long, who we share it with. | | [Compliance Summary](/legal/compliance) | DPDP, GDPR, CAN-SPAM, CASL, ePrivacy at a glance. | | [Incident Response](/legal/incident-response) | Breach notification, abuse reporting, security disclosures. | ## Applicable laws The Splashify Pro Email API operates from India and serves senders worldwide. The following laws apply to your use of the service: - **Information Technology Act, 2000** (India) — electronic records, intermediary liability, reasonable security practices. - **Digital Personal Data Protection Act, 2023** (India, "DPDP Act") — consent, purpose limitation, data principal rights. - **General Data Protection Regulation** (EU GDPR) — when you process personal data of EU/EEA residents. - **CAN-SPAM Act, 2003** (United States) — when sending to US recipients. - **CASL** (Canada) — when sending to Canadian recipients. - **ePrivacy Directive** (EU) — marketing consent and cookie rules. You are responsible for complying with the laws of every country your recipients reside in. Splashify Pro is responsible for operating the sending infrastructure in compliance with Indian law. ## Operator details | Field | Value | |---|---| | Legal entity | EvolvePro Tech Solutions Private Limited | | CIN | U62012WB2025OPC281483 | | GSTIN | 19AAJCE0527G1ZQ | | Registered office | Shimultala, Motiganj, Bongaon, North 24 Parganas, West Bengal — 743235, India | | Grievance Officer | grievance@splashifypro.in | | Data Protection Officer | dpo@splashifypro.in | | Abuse reports | abuse@splashifypro.in | | Security disclosures | security@splashifypro.in | ## Effective date and changes These documents are versioned. The "Effective date" at the top of each page tells you when the current version took effect. We will notify you of material changes at least **30 days** before they take effect via the email address on your account and a banner in the partner panel. If you disagree with a material change, you may close your account before the change takes effect. Continued use after the effective date constitutes acceptance. ======================================================================== ## Acceptable Use Policy URL: https://partner-docs.splashifypro.com/legal/acceptable-use-policy ======================================================================== # Acceptable Use Policy **Effective:** 1 May 2026 This Acceptable Use Policy ("AUP") applies to every email you send through the Splashify Pro Email API. Violations trigger automated enforcement (see [Auto-Action System](/legal/auto-actions)) and may result in account termination and reporting to law-enforcement or internet abuse desks. ## You agree NOT to send ### 1. Illegal content - Content that violates Indian law, including the Information Technology Act, 2000, the Bharatiya Nyaya Sanhita, 2023, the Indecent Representation of Women (Prohibition) Act, the POCSO Act, the Narcotic Drugs and Psychotropic Substances Act, or sectoral regulations issued by SEBI, RBI, IRDAI, or TRAI. - Content that violates the laws of the recipient's country (CAN-SPAM, GDPR, CASL, ePrivacy, etc.). - Defamatory, threatening, or harassing content; doxxing; incitement to violence; revenge porn. ### 2. Sexual content involving minors Content depicting, promoting, or facilitating the sexual exploitation of minors. Such content is reported to the National Cyber Crime Reporting Portal (cybercrime.gov.in) and the National Center for Missing and Exploited Children (NCMEC) without notice to the sender. ### 3. Spam and unsolicited mail - Email to recipients who have not given **clear, affirmative consent** to receive marketing email from your organization. - Email to addresses purchased, rented, scraped, or appended from third-party lists. - Email to addresses harvested from websites, directories, or WHOIS records. - Email to role-based addresses (info@, sales@, postmaster@) without a pre-existing relationship. - Email to disposable / temporary mailbox providers (10minutemail, guerrillamail, mailinator, etc.) with marketing intent. ### 4. Phishing, malware, and fraud - Phishing attempts. Brand impersonation. Credential harvesting. Forged sender identities. - Email containing or linking to malware, ransomware, keyloggers, cryptominers, exploit kits, or malicious browser extensions. - Advance-fee fraud ("Nigerian prince"), romance scams, fake-invoice schemes, business email compromise (BEC). - Pump-and-dump stock promotion. Pyramid schemes. Multi-level marketing where compensation is primarily from recruitment. ### 5. Regulated industries without authorization - Pharmaceuticals, prescription drugs, controlled substances, or recreational drugs. - Tobacco, e-cigarettes, vaping products. Alcohol marketing in jurisdictions where prohibited. - Firearms, ammunition, explosives, weapons of any kind. - Gambling, lottery tickets, or betting services in jurisdictions where unlicensed. - Adult content (pornography, escort services, dating with sexual intent). Splashify Pro does not service this category. - Cryptocurrency or token sales without prior written approval and proof of compliance with SEBI / FIU-IND rules. - Get-rich-quick schemes; "guaranteed income"; binary options; CFDs marketed to retail investors. ### 6. Privacy violations - Sending another person's personal data (PAN, Aadhaar, Voter ID, passport details, bank account numbers, health records) to anyone other than the data principal themselves, without explicit consent and a lawful basis under the DPDP Act 2023. - Doxxing, stalking, or any use of email to facilitate harassment campaigns. ### 7. Deceptive sender behaviour - Forging or misrepresenting the From address, Reply-To address, or any header field. - Routing email through Splashify Pro to bypass another email provider's deliverability filters or suppression list. - Using the API to test, measure, or evade spam filters of major inbox providers (Gmail, Outlook, Yahoo, Apple iCloud, etc.). ### 8. Infrastructure abuse - Reverse-engineering, attacking, or attempting to compromise the API or any internet infrastructure operated by us or any third party. - Sending email designed to trigger automated reply storms (out-of-office loops, vacation responder amplification). - Any activity that degrades the service for other partners. ## Recipient practices we require You must: 1. Honor unsubscribe requests within **10 calendar days**. We process the unsubscribe synchronously when a recipient clicks the link or uses Gmail/Outlook one-click unsubscribe; you must not re-add unsubscribed recipients to your sending list. 2. Maintain proof of consent for each recipient — date, source, wording of the consent prompt — for at least the duration of your sending relationship plus 3 years (DPDP Act §11 record-keeping). 3. Include accurate identification of you as the sender, a valid physical postal address, and a clear unsubscribe link in every marketing email. The Splashify Pro renderer ships these by default in the [Footer block](/api-reference/templates/create); do not strip them in raw-MIME sends. 4. Use a from-domain that you control and that has been [verified through our identity flow](/api-reference/identities/create). 5. Set up SPF, DKIM, and DMARC records for your sending domain. DMARC at `p=quarantine` or `p=reject` is required for production access. 6. Segment marketing and transactional email under separate [configuration sets](/knowledge-base/concepts/configuration-sets) so reputation issues don't bleed across. 7. Suppress hard-bounced and complainted addresses from your sending list immediately. The Splashify Pro suppression list does this automatically; do not work around it. ## Consequences of violation | Severity | Examples | Action | |---|---|---| | Minor | Single deliverability complaint, slightly elevated bounce rate | Email warning + dashboard flag | | Material | Sustained bounce > 5%, complaint > 0.1%, content edge cases | [Auto-pause](/legal/auto-actions) — manual reinstatement | | Severe | Phishing, malware, fraud, CSAM, sustained spam | Immediate termination + report to authorities | The auto-action system runs **without human intervention** and without prior notice on triggered thresholds. See [Auto-Action System](/legal/auto-actions) for the exact thresholds and recovery paths. ## Reporting violations If you believe another sender is abusing the service, email **abuse@splashifypro.in** with the offending message including full headers. We respond to abuse reports within **24 hours** on business days and within 72 hours on weekends. Reports involving CSAM, imminent harm, or active fraud are escalated immediately. ## Updates This AUP may change to reflect new threats or new legal requirements. Material changes get 30 days' notice; clarifications and additions to the banned-content list take effect immediately. Always check the "Effective" date at the top. ======================================================================== ## Anti-Spam Policy URL: https://partner-docs.splashifypro.com/legal/anti-spam ======================================================================== # Anti-Spam Policy **Effective:** 1 May 2026 Spam isn't just unwelcome — it's illegal in most jurisdictions and damages every legitimate sender on shared sending infrastructure. This page tells you what counts as consent, what list-acquisition practices are forbidden, and how unsubscribes flow through the system. ## What counts as consent You may send marketing email only to recipients who have given **clear, affirmative, recordable consent** to receive marketing email from your specific organization. Consent must: 1. Be **affirmative** — a deliberate action by the recipient (clicked a checkbox, completed a form, sent a written request). Pre-ticked checkboxes do not count. 2. Be **specific** — they consented to email from *your organization*, for *the purpose you'll use it for*. Consent given for "service updates" does not authorize promotional offers. 3. Be **informed** — you told them at the point of consent who you are, what they would receive, and how to withdraw consent. 4. Be **recordable** — you can produce, on request, the date, timestamp, IP address, and exact wording of the consent prompt. Single opt-in is acceptable for most jurisdictions. **Double opt-in (confirmed opt-in)** is required for senders targeting Germany, Austria, Brazil, and is strongly recommended for all sending lists because it cuts complaints and bounces dramatically. ### Consent does NOT include - Membership in a public directory. - Publication of the address on a website. - Provision of the address as part of an unrelated transaction (e.g. someone bought a product — that doesn't authorize marketing unrelated to the purchase). - Forwarded consent ("My friend gave me your email"). - Contractual obligation of the recipient to receive your messages in another channel (e.g. they are your employee or your customer). ### Implied consent (very limited) Some jurisdictions recognize implied consent for transactional or relationship messages. Even where lawful, you should: - Limit messages to the relationship in question (order confirmations, account security alerts, document signings). - Honor unsubscribes the same way you would for marketing. - Refresh consent at least every 24 months. ## Banned list-acquisition practices You must not send through Splashify Pro to addresses obtained through: - **Purchase, rental, or trade** of any list, regardless of the vendor's claims about consent or quality. - **Web scraping** — collecting addresses from websites, social networks, directories, or any place addresses are published. - **Email appending** — taking a name and using a service to "find" the email address. - **Dictionary attacks** — generating addresses by combining names, numbers, or words against a domain. - **Co-registration** — inheriting consent from a partner's signup form unless the partner clearly named your organization at the point of opt-in. - **Contests, sweepstakes, raffles** — unless email consent was a separate opt-in from entering the contest. - **Customer data acquired through merger, acquisition, or bankruptcy** unless consent was specifically transferred to the acquiring entity in the original opt-in language. Sending to any address obtained through the above triggers [Auto-Action System](/legal/auto-actions) responses ranging from auto-pause to termination depending on volume and pattern. ## Unsubscribe requirements ### Every marketing email must include 1. A **functional unsubscribe link** that: - Works without requiring login. - Removes the recipient from your sending list within **10 calendar days** (we do this within seconds). - Does not require entering personal data. - Does not redirect through tracking domains that strip the request. 2. The **`List-Unsubscribe` header** in the format defined by RFC 2369: ``` List-Unsubscribe: , ``` 3. The **`List-Unsubscribe-Post` header** for one-click unsubscribe per RFC 8058, supported by Gmail and Outlook: ``` List-Unsubscribe-Post: List-Unsubscribe=One-Click ``` The Splashify Pro renderer adds all three automatically when you send via `/send`, `/send-template`, or `/send-bulk`. For raw-MIME sends via `/send-raw`, you must include them yourself. ### What unsubscribe means Once a recipient unsubscribes from any of your messages, you may not email that address again from any sending domain you control, regardless of the configuration set or template used. Splashify Pro enforces this account-wide via the [suppression list](/api-reference/suppression/list). ### Re-engagement If an address has unsubscribed from your marketing list, you may not re-add it without **fresh affirmative consent** captured after the unsubscribe. You may continue to send transactional messages (order confirmations, security alerts, legally-mandated notices) to that address. ## Spam laws we enforce Splashify Pro requires you to comply with anti-spam laws of every country your recipients reside in. We particularly check against: ### CAN-SPAM Act, 2003 (US) - Accurate From, Reply-To, and routing information. - Truthful Subject lines. - Identification as an advertisement (where applicable). - A valid physical postal address of the sender. - Functional unsubscribe processed within 10 business days. ### CASL (Canada) - Express consent (verbal recording or written log) for marketing. - Identification of the sender, including any person on whose behalf the message is sent. - Functional unsubscribe processed within 10 business days. - Penalties up to CAD 10 million per violation. ### GDPR (EU) - Lawful basis for processing — typically consent or legitimate interest balanced against the recipient's privacy rights. - Right to object to direct marketing — unsubscribe must be available "by electronic means". - Records of consent must be auditable. ### DPDP Act, 2023 (India) - Notice at the point of collection in the language of the recipient's choice (the 22 official languages of India). - Consent must be free, specific, informed, unconditional, and unambiguous. - Withdrawal of consent must be as easy as giving it. - Penalties up to INR 250 crore. ### ePrivacy Directive (EU) - Requires prior opt-in consent for marketing email to natural persons, even where the underlying GDPR basis would be legitimate interest. ### LGPD (Brazil) - Same consent rigor as GDPR, with explicit prior consent required for marketing in most contexts. ## Spamtrap addresses Spamtraps are addresses planted by inbox providers and reputation services to detect senders who don't have proper consent. They never opt in, so any send to a spamtrap is by definition non-consensual. We auto-detect spamtrap hits via reputation feeds. A single hit produces an auto-pause; two within 7 days is treated as a deliberate list-acquisition violation and may result in termination. The most common cause of spamtrap hits is purchased lists. The second most common is failure to clean a long-dormant list before re-engaging it. Don't do either. ## Engagement-based hygiene Even with proper consent, recipients lose interest. Lists go stale. Bounces and complaints rise. To stay deliverable: - **Monitor engagement.** Open and click rates trending toward zero in a segment is a signal to re-engage or remove. - **Set a freshness threshold.** Drop addresses with no opens or clicks in 12 months. Major inbox providers consider sustained non-engagement a deliverability negative. - **Segment new acquisitions.** Recipients who signed up in the last 30 days behave differently than recipients from 5 years ago. Send them through different [configuration sets](/knowledge-base/concepts/configuration-sets) so reputation issues don't bleed. - **Honor frequency preferences.** If a recipient asks for "monthly only" and you send weekly, you'll see complaints. ## Reporting spam to us If you've received spam from a Splashify Pro sender, forward the message **with full headers preserved** to **abuse@splashifypro.in**. We respond within 24 hours on business days. Acting on abuse reports is mandatory under the IT Act and we take it seriously. ======================================================================== ## Auto-Action System URL: https://partner-docs.splashifypro.com/legal/auto-actions ======================================================================== # Auto-Action System **Effective:** 1 May 2026 The Splashify Pro Email API enforces the [Acceptable Use Policy](/legal/acceptable-use-policy) automatically. Thresholds are checked continuously; actions fire **without human intervention** when triggered. This page documents every threshold so you know exactly what happens, why, and how to recover. ## Why we do this automatically A single bad sender hurts every other sender on shared sending infrastructure. Inbox providers (Gmail, Outlook, Yahoo) score the sending domain and IP reputation collectively; one phishing run can trigger blocklisting that affects unrelated partners for days. By acting in seconds rather than waiting for a human review queue, we protect: - **Other partners** from collateral deliverability damage. - **Recipients** from spam, phishing, and fraud. - **Splashify Pro's reputation** as a deliverable email API. ## Reputation thresholds (rolling 14-day window) Every send produces a delivery outcome (sent / delivered / bounced / complained / rejected). We compute the rolling 14-day rate per account and per [configuration set](/knowledge-base/concepts/configuration-sets). | Metric | Healthy | At-risk | Auto-paused | |---|---|---|---| | Bounce rate | ≤ 3% | 3% – 5% | > 5% (or > 10% in any single day) | | Complaint rate | ≤ 0.05% | 0.05% – 0.1% | > 0.1% (or > 0.5% in any single day) | | Spamtrap hits | 0 | 1 | ≥ 2 within 7 days | **At-risk** triggers a warning email and a dashboard banner. Sending continues but at reduced peak rate until the rolling rate falls back to healthy. **Auto-paused** halts every send from the account. New send requests return `403 account_paused` with the reason. The pause is removed only after manual review and remediation — it does not auto-clear. ## Content-based triggers Every outbound message passes through a content classifier before it hits the network. The classifier looks for known phishing kits, malware payload signatures, and the banned categories listed in the [AUP](/legal/acceptable-use-policy). | Trigger | Action | |---|---| | Known phishing kit / brand impersonation | **Block + auto-pause** | | Malware URL / executable attachment | **Block + auto-pause** | | CSAM heuristic | **Block + immediate termination + report** | | Banned regulated category | Block individual send, warn | | URL on Spamhaus DBL / SURBL | Block individual send, warn | The block is reflected in the response: the API returns 200 with `status: "rejected"` and a reason field; the message is never queued. ## Behavioural triggers Some abuse patterns aren't visible from a single message — they only emerge across many. | Pattern | Trigger | Action | |---|---|---| | Recipient list churn | > 50% of recipients are new in a single send batch | At-risk flag + slower send rate | | Disposable mailbox saturation | > 5% of sends to known disposable domains | Block disposable domains, warn | | Hard-bounce ratio on first send to a list | > 15% on first 1,000 messages | Pause batch, warn | | Spamtrap hit | ≥ 1 known spamtrap address in recipient list | Auto-pause | | Repeat AUP violation | Second [AUP](/legal/acceptable-use-policy) breach within 30 days of reinstatement | Termination | ## Sandbox guardrails Every new account starts in **sandbox mode** so first-send mistakes don't damage your reputation. - 200 emails per 24 hours - 1 email per second peak rate - Recipients must be on your verified identity list - `Reply-To` header is automatically set to your registered email so bounces reach you [Request production access](/api-reference/production-access/submit) when you're ready. Approval lifts the sandbox cap; it does not lift the AUP thresholds. ## Recovery paths ### From At-Risk You usually recover automatically when the rolling 14-day rate falls back below the warning threshold. Practical steps: 1. Stop sending to the segment causing bounces or complaints. 2. Clean your list — drop hard bounces (already auto-suppressed), drop addresses that haven't engaged in 90 days, fix any obvious typos. 3. Improve content — clearer From name, accurate subject lines, no misleading preview text. 4. Slow down. New lists should warm up over 7–14 days, not blast all at once. ### From Auto-Paused 1. Identify the cause. The dashboard shows which threshold tripped and a sample of the offending sends. 2. Email **abuse-review@splashifypro.in** with: - Your account ID. - A short explanation of what went wrong. - The remediation steps you've taken (list cleaning, content changes, sending-source segmentation). 3. We review within **2 business days**. If the remediation looks genuine and the violation wasn't deliberate, we lift the pause and place the account on a 30-day probation. A second pause during probation is treated as a [Severe](#severity-levels) violation. ### From Termination Termination is permanent for the account and the legal entity that operated it. Creating a new account to circumvent termination is itself a violation and is detected by our duplicate-detection system (domain + business identifier + payment method overlap). If you believe the termination was an error, email **grievance@splashifypro.in** within **15 days** of termination. We respond within 30 days under the IT (Intermediary Guidelines) Rules. ## Severity levels | Level | Examples | Default action | |---|---|---| | **Minor** | Single user complaint, deliverability blip | Warning email | | **Material** | Sustained reputation breach, AUP edge cases | Auto-pause | | **Severe** | Phishing, malware, fraud, repeated material breaches | Termination | | **Critical** | CSAM, imminent harm, active fraud | Termination + report to authorities | ## Appeal rights You have the right under the IT (Intermediary Guidelines and Digital Media Ethics Code) Rules, 2021 to appeal any account action. Send appeals to **grievance@splashifypro.in**. The Grievance Officer acknowledges within **24 hours** and resolves within **15 days**. If you are dissatisfied with the Grievance Officer's decision, you may escalate to the Grievance Appellate Committee constituted by the Ministry of Electronics and Information Technology under Rule 3A. ## What we never do automatically - Read the body of your emails for any purpose other than the classifier described above. We do not train AI on your content. - Share your sending data with competitors, marketing networks, or any third party who hasn't signed our DPA. - Disclose your sending statistics to your competitors. Reputation data is yours alone. - Modify the body of your emails. We add `List-Unsubscribe` headers and (when requested) tracking pixels — we never rewrite content. ## Transparency Every auto-action writes an entry to your account's audit log. You can [query the audit log via the API](/api-reference/quotas/list-events). We do not delete or modify audit entries; they are append-only and preserved for at least the lifetime of your account plus 3 years (consistent with DPDP Act §11(3) record-keeping). ======================================================================== ## Compliance Summary URL: https://partner-docs.splashifypro.com/legal/compliance ======================================================================== # Compliance Summary **Effective:** 1 May 2026 This page summarizes the major laws and standards that apply when you use the Splashify Pro Email API. It is **informational** — it is not a substitute for legal advice from a lawyer qualified in your jurisdiction. The full obligations are documented in the [API Terms of Service](/legal/terms-of-service), [Acceptable Use Policy](/legal/acceptable-use-policy), [Anti-Spam Policy](/legal/anti-spam), [Data Processing Agreement](/legal/data-processing-agreement), and [Privacy Policy](/legal/privacy-policy). ## India ### Information Technology Act, 2000 The IT Act governs electronic records, intermediary liability, and reasonable security practices. **Our compliance:** - We operate as an "intermediary" under §2(1)(w) and observe intermediary due diligence under Rule 3 of the IT (Intermediary Guidelines and Digital Media Ethics Code) Rules, 2021. - We have appointed a [Grievance Officer](/legal/privacy-policy#13-grievance-redressal) with public contact details. - Reasonable security practices implemented per §43A and ISO 27001- aligned controls. - Take-down on receipt of court order or government notification per §69A and Rule 3(1)(d). **Your obligation:** Don't send content prohibited by §66, §67, §69 of the IT Act (impersonation, obscenity, defamation, hate speech). ### Digital Personal Data Protection Act, 2023 India's first comprehensive privacy law. Key concepts: - **Data Fiduciary** — you, when sending to your end-customers. - **Data Processor** — Splashify Pro, processing recipient data on your instructions. - **Data Principal** — the recipient (or you, when we hold your account data). **Key obligations on you (Data Fiduciary):** | Obligation | What it means for email senders | |---|---| | Lawful purpose (§4) | Only send for a lawful purpose with a valid lawful basis | | Notice (§5) | Tell recipients what data you collect, for what purpose, and how to withdraw consent — at the point of collection, in their preferred language | | Consent (§6) | Free, specific, informed, unconditional, unambiguous; clear affirmative action | | Withdrawal (§6(4)) | As easy to withdraw as to give | | Purpose limitation (§7) | Don't repurpose data without fresh consent | | Storage limitation (§8(7)) | Delete when purpose is fulfilled | | Data Principal rights (§11–13) | Honor access, correction, erasure, grievance | | Breach notification (§8(6)) | Notify Data Protection Board "without delay" | | Children (§9) | Don't process personal data of minors without parental consent; no behavioral monitoring | **Significant Data Fiduciaries** (designated by the Central Government for high-volume or sensitive processing) have additional obligations including a Data Protection Officer, audits, and DPIA. We act as DPF and meet those requirements; you should evaluate whether you do too. **Penalties:** Up to **INR 250 crore** per violation. ### Telecom Commercial Communications Customer Preference Regulations, 2018 (TRAI) Primarily targets SMS and voice but referenced in connection with unsolicited commercial communications. Email is largely governed by the IT Act and DPDP. Senders should observe DND (Do Not Disturb) preferences on multi-channel campaigns. ### Income Tax Act, 1961 — Record retention We retain billing records for 8 years per §44AA. Tax invoices issued through Zoho Payments include GSTIN and HSN/SAC codes per GST law. ## European Union / EEA ### General Data Protection Regulation (GDPR) — Regulation 2016/679 Applies to processing of personal data of EU/EEA residents, regardless of where the processor is located. **Roles:** You are the **Controller**; Splashify Pro is the **Processor**. The [DPA](/legal/data-processing-agreement) is the Article 28 written contract. **Key obligations on you (Controller):** - Lawful basis (Article 6) — for marketing email, this is typically consent (Article 6(1)(a)) or legitimate interest (Article 6(1)(f)) balanced against the recipient's privacy rights. - Information at collection (Articles 13–14). - Purpose limitation, data minimization, storage limitation (Article 5). - Records of processing (Article 30). - Data Subject rights (Articles 15–22). - Breach notification within 72 hours (Article 33). - Data Protection Impact Assessment for high-risk processing (Article 35). - Lawful international transfers (Articles 44–49). **Penalties:** Up to **EUR 20 million or 4% of global annual turnover**, whichever is higher. ### ePrivacy Directive — 2002/58/EC (as amended) Requires **prior opt-in consent** for direct marketing email to natural persons in the EU/EEA, even where GDPR would allow legitimate interest. Limited "soft opt-in" exception for similar products to existing customers (Article 13(2)) with each message offering an unsubscribe. ### LGPD (Brazil) — Lei Geral de Proteção de Dados Pessoais GDPR-aligned. Same consent rigor and rights. We comply for transfers involving Brazilian residents. ## United States ### CAN-SPAM Act, 2003 Federal law. Applies to commercial email sent to US recipients. **Key obligations:** - Accurate From, Reply-To, and routing information. - Truthful Subject lines. - Identification as advertisement (where applicable). - Valid physical postal address of the sender. - Functional unsubscribe processed within 10 business days. **Penalties:** Up to **USD 50,120 per email** under FTC enforcement. ### State laws California (CCPA/CPRA), Virginia (VCDPA), Colorado (CPA), and other state privacy laws may impose additional requirements depending on the recipients you target. ### TCPA — Telephone Consumer Protection Act Primarily covers calls and SMS, but cited in unsolicited-message litigation. Email senders should be aware in case of mixed-channel campaigns. ## Canada ### CASL — Canada's Anti-Spam Law Among the strictest anti-spam laws globally. **Key obligations:** - **Express consent** (verbal recording or written log) for marketing. - Identification of the sender, including any person on whose behalf the message is sent. - Functional unsubscribe processed within 10 business days. **Penalties:** Up to **CAD 10 million per violation**. ## Other notable jurisdictions | Country | Key law | Notes | |---|---|---| | UK | UK GDPR + Data Protection Act 2018 | Substantially aligned with EU GDPR post-Brexit | | Australia | Spam Act 2003 + Privacy Act 1988 | Express, inferred, or implied consent | | Singapore | PDPA 2012 + Spam Control Act 2007 | Opt-in / opt-out hybrid | | UAE | PDPL 2021 | GDPR-aligned, UAE-specific bases | | Switzerland | revFADP 2023 | GDPR-aligned | ## Standards and certifications We work toward and align with: - **ISO 27001** (Information Security Management System) — design aligned; certification roadmap on the trust portal. - **SOC 2 Type 2** — audit roadmap on the trust portal. - **PCI DSS** — payment processing is delegated to Zoho Payments (PCI Level 1); we never store cardholder data. Trust portal: **trust.splashifypro.com** (forthcoming). ## What this means in practice If you are sending email through Splashify Pro: 1. Have a **lawful basis** for every recipient on your list. For most, that's clear affirmative consent. 2. Maintain a **record** of consent — what was said, when, from where. 3. Include in every marketing email: accurate sender identity, a valid physical address, a functional unsubscribe link. 4. Honor unsubscribes **immediately** (we do this within seconds). 5. Don't use the API for content prohibited by the [Acceptable Use Policy](/legal/acceptable-use-policy). 6. If you experience a data breach involving recipient data, notify us at **security@splashifypro.in** within 24 hours so we can support you in the breach-notification timeline. ## Get help For specific compliance questions, consult a lawyer in the relevant jurisdiction. For Splashify Pro's compliance posture, email **legal@splashifypro.in**. For data-protection enquiries, email **dpo@splashifypro.in**. ======================================================================== ## Data Processing Agreement URL: https://partner-docs.splashifypro.com/legal/data-processing-agreement ======================================================================== # Data Processing Agreement **Effective:** 1 May 2026 This Data Processing Agreement ("DPA") forms part of the [API Terms of Service](/legal/terms-of-service) between you (the "Controller" / "Data Fiduciary") and EvolvePro Tech Solutions Private Limited ("Splashify Pro", the "Processor" / "Data Processor"). It applies whenever you use the Splashify Pro Email API to process Personal Data on behalf of yourself or your end-customers. ## 1. Definitions Terms used here have the meanings given in the **Digital Personal Data Protection Act, 2023** (India) ("DPDP") and the **General Data Protection Regulation** (EU) ("GDPR"). Where the two diverge, the more protective definition applies. - **Personal Data** — Information about an identified or identifiable natural person ("Data Principal" / "Data Subject"), including but not limited to email address, IP address, device identifiers, and message content. - **Controller / Data Fiduciary** — The party that determines the purposes and means of processing. - **Processor / Data Processor** — The party that processes Personal Data on behalf of the Controller. - **Sub-processor** — A third party engaged by the Processor to process Personal Data. - **Personal Data Breach** — A breach of security leading to unauthorized access, disclosure, alteration, or destruction of Personal Data. ## 2. Roles For Personal Data sent or processed through the Splashify Pro Email API on your instruction: - **You are the Data Fiduciary / Controller.** You determine which recipients to email, what content to send, and the lawful basis for the processing. - **Splashify Pro is the Data Processor.** We process Personal Data only on your documented instructions, expressed through your use of the API. For Personal Data we collect about you (your account, payment information, usage logs), Splashify Pro acts as the **Data Fiduciary** in its own right; that processing is governed by our [Privacy Policy](/legal/privacy-policy). ## 3. Subject matter and duration | Field | Detail | |---|---| | Subject matter | Sending email on behalf of the Controller | | Duration | Term of the API Terms of Service plus retention periods in §7 | | Nature of processing | Receiving email content + recipient list, signing with DKIM, transmitting via SMTP, storing delivery outcomes | | Purpose | Email transmission and delivery analytics | | Categories of Data | Email addresses, names, IP addresses, message content, headers, delivery metadata | | Categories of Data Principals | Recipients of email sent through the API; senders' employees who configure the account | ## 4. Processor obligations Splashify Pro will: 1. Process Personal Data only on the Controller's documented instructions, including transfers, unless required to do otherwise by Indian or EU law (in which case we will inform the Controller before processing, unless prohibited from doing so). 2. Ensure that personnel authorized to process Personal Data have committed themselves to confidentiality. 3. Implement appropriate technical and organizational measures to ensure a level of security appropriate to the risk, including those listed in §9. 4. Engage Sub-processors only as permitted under §5. 5. Assist the Controller in fulfilling Data Principal rights (access, correction, erasure, portability, objection) within the timeframes set by applicable law. 6. Notify the Controller without undue delay (and within 72 hours) of becoming aware of a Personal Data Breach affecting their data. 7. Make available all information necessary to demonstrate compliance and contribute to audits as set out in §11. 8. Delete or return all Personal Data at the end of the provision of services, except as required to be retained by law. ## 5. Sub-processors Splashify Pro engages the following Sub-processors for the operation of the service. Each is bound by data-protection obligations no less protective than those in this DPA. | Sub-processor | Purpose | Region | |---|---|---| | Cloud infrastructure provider | Compute, storage, networking | India (primary) | | DNS resolution | Delivering email to recipient mail servers | Global anycast | | Inbox feedback loops (Gmail Postmaster, Microsoft SNDS, Yahoo CFL) | Reputation monitoring | US/EU | | Anti-abuse intelligence | Spamtrap, blocklist, malware detection | US/EU | | Payment gateway (Zoho Payments) | Account billing | India | | Telemetry / error monitoring | Service reliability | US (encrypted) | | Customer-support CRM | Handling support tickets | India | We will give the Controller **30 days' prior notice** of changes to the list of Sub-processors. The Controller may object to a change in writing within 15 days; if objection is sustained on legitimate data-protection grounds, the Controller may terminate the relevant service without penalty. ## 6. International transfers Personal Data is primarily stored and processed in India. Some Sub-processors are located outside India. - **DPDP Act:** The Indian government may, by notification, restrict transfers to specified countries. We comply with any such notification. - **GDPR:** Transfers from the EU/EEA to India and to non-adequate jurisdictions are made on the basis of the **Standard Contractual Clauses** (Module 2 — controller to processor) and supplementary measures including encryption in transit and at rest. ## 7. Retention | Data category | Retention | Purpose | |---|---|---| | Email content (body, attachments) | 30 days from send | Bounce diagnosis, support | | Delivery metadata (status, bounce reason, click/open events) | 18 months | Reputation analytics, audit | | Suppression list entries | Indefinite, until removed by Controller | Compliance with §10 of CAN-SPAM and similar laws | | Audit log | Term of agreement + 3 years | DPDP §11(3) record-keeping | | Account billing records | 8 years from invoice | Indian tax law (§44AA Income Tax Act) | On termination, we delete email content and delivery metadata within 30 days. Suppression lists are retained as legal-compliance records unless the Controller specifically requests their deletion. Audit logs and billing records are retained for the legal periods listed above. ## 8. Data Principal rights The Controller is responsible for responding to Data Principal requests. Splashify Pro will assist the Controller through: - API endpoints to query a recipient's status (delivery history, suppression status, click/open events). - Tools to delete a recipient's data on Controller request (`DELETE /suppression/{email}` retains compliance evidence; contact **dpo@splashifypro.in** for full erasure). - Within **48 hours** for requests forwarded to us in writing. ## 9. Security measures Splashify Pro implements security measures appropriate to the risk, including: - **Encryption in transit** — TLS 1.2 or higher for all API calls and SMTP submission to recipient mail servers (with opportunistic STARTTLS where the recipient supports it). - **Encryption at rest** — Storage volumes encrypted with industry-standard algorithms (AES-256 or equivalent). - **Access control** — Role-based access for Splashify Pro personnel; multi-factor authentication; principle of least privilege; audit logging of all administrative access. - **Secure development** — Code review for all changes, automated vulnerability scanning, periodic penetration testing. - **Network controls** — Network segmentation, firewall rules, intrusion detection. - **Backup and recovery** — Encrypted backups with regular restore testing; documented disaster-recovery plan. - **Personnel** — Background checks for all engineers handling production systems; mandatory annual security training. ## 10. Breach notification In the event of a Personal Data Breach affecting Controller data, Splashify Pro will: 1. Notify the Controller **without undue delay and within 72 hours** of becoming aware of the Breach. 2. Provide all reasonable information necessary for the Controller to meet its own notification obligations under DPDP Act §8(6) (notify the Data Protection Board) and GDPR Articles 33 and 34 (notify the supervisory authority and, where required, the Data Subjects). 3. Cooperate with the Controller's investigation and remediation. The notification will include: nature of the Breach, categories and approximate number of Data Principals affected, categories and approximate number of records affected, likely consequences, and measures taken or proposed to address it. ## 11. Audits The Controller may audit Splashify Pro's compliance with this DPA: - By reviewing third-party audit reports (SOC 2, ISO 27001) we publish on request. - By submitting written questions and requesting documentary evidence; we respond within 30 days. - For Controllers processing high volumes of sensitive data, on-site audits can be arranged with 60 days' notice and at the Controller's expense, subject to confidentiality terms and reasonable scope limits. Audit rights do not extend to systems or data of other Controllers. ## 12. Liability Liability for breaches of this DPA is governed by Section 13 of the [API Terms of Service](/legal/terms-of-service). In addition, Splashify Pro indemnifies the Controller for direct losses arising from Splashify Pro's failure to comply with its Processor obligations under DPDP Act §8 and GDPR Article 28, subject to the cap and exclusions in the Terms of Service. ## 13. Termination This DPA terminates automatically on termination of the API Terms of Service. The deletion obligation in §7 survives termination. The audit and breach-notification rights survive for 1 year after termination for events that occurred during the term. ## 14. Order of precedence In case of conflict between this DPA and the API Terms of Service, this DPA prevails for matters of data protection. In case of conflict with mandatory provisions of the DPDP Act or GDPR, the law prevails. ## 15. Governing law This DPA is governed by the laws of India. Disputes are subject to the exclusive jurisdiction of the courts of Kolkata, West Bengal, without prejudice to the Controller's mandatory consumer or data-protection rights under the DPDP Act or GDPR. ## Contact | For | Email | |---|---| | Data-protection enquiries | dpo@splashifypro.in | | Grievance Officer | grievance@splashifypro.in | | Breach notifications | security@splashifypro.in | | Sub-processor change objections | dpo@splashifypro.in | By signing the API Terms of Service or sending your first request, you accept this DPA on behalf of yourself and any end-customer for whom you act. ======================================================================== ## Incident Response URL: https://partner-docs.splashifypro.com/legal/incident-response ======================================================================== # Incident Response **Effective:** 1 May 2026 This page tells you how to report security issues, abuse, and data incidents — and what to expect from us when you do. ## Quick reference | Type of report | Email | Response time | |---|---|---| | Security vulnerability in our service | security@splashifypro.in | 24 hours | | Suspected breach of recipient data | security@splashifypro.in | 24 hours / 72 hours formal | | Spam, phishing, or abuse from a Splashify Pro sender | abuse@splashifypro.in | 24 hours business / 72 hours weekend | | Privacy / data-protection issue | dpo@splashifypro.in | 30 days statutory | | Grievance redressal | grievance@splashifypro.in | 24 hours acknowledge / 15 days resolve | | Suspected unauthorized access to **your** account | security@splashifypro.in + lock immediately | Immediate | ## Reporting a security vulnerability We welcome reports from security researchers. To report: 1. Email **security@splashifypro.in** with: - Steps to reproduce. - Expected vs actual behavior. - Impact assessment (what could an attacker do?). - Your name and contact details (or pseudonym if you prefer). 2. Encrypt sensitive details with our PGP key (published on the trust portal). For most reports, plain email is fine. 3. Do **not** publicly disclose until we have had a reasonable chance to investigate and fix. ### Our commitments - We acknowledge within 24 hours. - We provide a tracking ID and a single point of contact. - We share periodic updates while we triage and remediate. - We disclose the fix to you when it ships. - We credit researchers in our public security advisories (with consent) on the trust portal. ### Safe harbor Good-faith vulnerability research conducted within these guidelines is authorized — we will not pursue civil or criminal action under the Information Technology Act §43A or §66, the Indian Penal Code, or the Computer Fraud and Abuse Act for testing that: - Stays within the scope of accounts you control. - Avoids accessing or modifying other partners' data. - Avoids degrading service for other partners (no DDoS, no resource exhaustion). - Avoids social engineering of our staff. - Reports findings privately to security@splashifypro.in before public disclosure. ### Out of scope - Findings against our marketing site (splashifypro.com) — please report to the marketing-site security team via the contact form. - Theoretical vulnerabilities without proof of exploitability. - Self-XSS, missing security headers without an exploitation path, rate-limit absence on non-sensitive endpoints. - Findings against third-party services we integrate with — report to that service's security team. ## Reporting abuse If you've received spam, phishing, or fraudulent email from a sender using Splashify Pro: 1. Forward the email **with full headers preserved** to **abuse@splashifypro.in**. Include any URLs you observed and any harm that resulted (financial loss, identity theft, etc.). 2. We acknowledge within 24 hours on business days, 72 hours on weekends. 3. We investigate, take action under the [Auto-Action System](/legal/auto-actions), and respond with the outcome where confidentiality permits. For urgent matters (active phishing campaign, financial fraud in progress), include "URGENT" in the subject line. We have on-call escalation outside business hours for these. We **act on every abuse report**. Sender accounts that produce multiple credible reports are paused pending review even before our own automated detection triggers. ## Suspected breach of recipient data If you suspect a breach affecting **recipient** data (e.g. unauthorized access to your account that exposed your sending list): 1. **Lock your account immediately** — change your password, rotate all API keys, terminate active sessions from the panel. 2. Email **security@splashifypro.in** with subject "BREACH" and describe what happened and when. 3. We respond within 24 hours with: - Confirmation we received the report. - A tracking ID. - Our preliminary assessment of any cross-tenant exposure. 4. If the incident triggers our Processor obligation under the [DPA §10](/legal/data-processing-agreement#10-breach-notification), we provide formal notification within 72 hours including all information you need to fulfill your own notification obligations under DPDP Act §8(6) or GDPR Articles 33-34. If the breach is on **your** side and recipient data was exposed via your own systems (not via Splashify Pro), we still cooperate fully with your investigation and provide any supporting data we hold. ## Suspected unauthorized access to your account If you see activity in your Splashify Pro account that you don't recognize: 1. **Immediately**: - Sign out everywhere from the Partner Panel (Settings → Sessions → Revoke all). - Rotate every API key (Settings → API Keys → Rotate). - Change your password. - Enable two-factor authentication if not already on. 2. Email **security@splashifypro.in**: - Account ID. - The activity you didn't recognize (timestamps, sends, config changes). - What credentials you suspect were exposed. 3. We provide an account audit log going back 90 days within 24 hours, free of charge. 4. We freeze billing impact during the investigation (sends made during the unauthorized window are not retroactively credited automatically — but we credit them on case review). ## Privacy / data-protection issues For: - Data-Subject Rights requests (access, correction, erasure, portability). - Concerns about how we handle your account data. - Sub-processor objections. Email **dpo@splashifypro.in**. We respond within 30 days (DPDP Act) or one month (GDPR), extendable for complex requests. For grievance redressal under the IT (Intermediary Guidelines) Rules, 2021, escalate to **grievance@splashifypro.in**. The Grievance Officer acknowledges within 24 hours and resolves within 15 days. ## What we publish about incidents After remediation, we publish: - **Status-page incidents** — real-time during the incident, post-mortem within 14 days, at status.splashifypro.com. - **Security advisories** — on the trust portal (forthcoming) for vulnerabilities of medium severity or higher, including affected versions, mitigations, and credit to researchers (with consent). - **Annual transparency report** — summary of law-enforcement requests we received and our response. We do **not** publish: - Customer-specific incidents — these are communicated only to the affected customer. - Full technical details of fixed vulnerabilities while exploitation in the wild is plausible. ## Government and law-enforcement requests We require **valid legal process** for any disclosure of customer data — search warrant, court order, or specific statutory authority under the IT Act, CrPC, or equivalent in the requesting jurisdiction. We: - Verify the authenticity of the request. - Narrow the scope wherever the request is overbroad. - Provide notice to the affected customer **before** disclosure unless prohibited by law (e.g. a non-disclosure order). - Reject voluntary requests not backed by lawful authority. Annual statistics are published in our transparency report. ## Drills and tabletop exercises We run quarterly tabletop exercises covering: - Confirmed external compromise of the Service. - Sub-processor breach notification. - Coordinated phishing campaign by a banned sender. - Loss of access to a single sending region. Findings feed back into runbooks and engineering priorities. ## Contact escalation chain If you don't get a response in the windows above, escalate: 1. The original mailbox (security / abuse / dpo / grievance). 2. **Grievance Officer:** grievance@splashifypro.in 3. **Postal address:** Grievance Officer, EvolvePro Tech Solutions Private Limited, Shimultala, Motiganj, Bongaon, North 24 Parganas, West Bengal — 743235, India. 4. For matters governed by the IT Rules, 2021 — **Grievance Appellate Committee** under Rule 3A. 5. For matters governed by the DPDP Act — **Data Protection Board of India**. 6. For matters governed by the GDPR — your **supervisory authority** (CNIL, ICO, Garante, etc.). ======================================================================== ## Privacy Policy URL: https://partner-docs.splashifypro.com/legal/privacy-policy ======================================================================== # Privacy Policy **Effective:** 1 May 2026 This Privacy Policy explains how **EvolvePro Tech Solutions Private Limited** ("Splashify Pro", "we", "us") handles personal information about partners using the Splashify Pro Email API and the Partner Panel at partner.splashifypro.com. This policy covers personal information about **you** (the partner). For information about how we process **email recipient** data on your behalf, see the [Data Processing Agreement](/legal/data-processing-agreement). ## 1. Who we are Splashify Pro is a brand of EvolvePro Tech Solutions Private Limited, a company incorporated in India (CIN U62012WB2025OPC281483, GSTIN 19AAJCE0527G1ZQ), with registered office at Shimultala, Motiganj, Bongaon, North 24 Parganas, West Bengal — 743235, India. For partner-account data we are the **Data Fiduciary** (DPDP Act terminology) / **Controller** (GDPR terminology). ## 2. What we collect about you | Category | Examples | Source | |---|---|---| | Account identifiers | Name, email, mobile number, business name, GSTIN, address | You, at signup or in /billing | | Authentication | Hashed password, OTP codes, JWT session, two-factor settings | You, at signup or login | | Billing | Invoice details, payment instrument tokens (we never store card numbers), GST treatment | You + Zoho Payments | | Usage data | API requests, IP address, user-agent, timestamps, error responses | Automatic from API and panel | | Account telemetry | Wallet balance, send volume, reputation score, configuration sets created | Automatic | | Support records | Tickets, chat transcripts, screenshots you provide | You, when contacting support | We do **not** intentionally collect special-category data about you (health, biometric, religious, etc.). If you provide such data voluntarily (e.g. in a support message), we minimize handling and delete it as soon as the support issue is resolved. ## 3. Why we use it We process your information for the following purposes, with the lawful basis under Indian and EU law: | Purpose | DPDP basis | GDPR basis | |---|---|---| | Provide and operate the Service | Performance of contract | Article 6(1)(b) | | Bill you and collect payment | Performance of contract | Article 6(1)(b) | | Detect and prevent abuse, fraud, and AUP violations | Legitimate use; legal obligation | Article 6(1)(f); 6(1)(c) | | Comply with tax, accounting, anti-money-laundering, and IT Act obligations | Legal obligation | Article 6(1)(c) | | Deliver service notifications and incident alerts | Performance of contract | Article 6(1)(b) | | Send product updates, newsletters, and marketing | Consent | Article 6(1)(a) | | Improve the Service through aggregated analytics | Legitimate use | Article 6(1)(f) | | Defend ourselves in legal proceedings | Legitimate use; legal obligation | Article 6(1)(f); 6(1)(c) | You can withdraw consent for marketing at any time from the Partner Panel or by clicking unsubscribe. ## 4. Who we share it with We share your information only with: - **Sub-processors** listed in the [DPA §5](/legal/data-processing-agreement#5-sub-processors). They process data on our instructions and under contracts that mirror this policy. - **Tax authorities** (GST, Income Tax, etc.) when required by law. - **Law-enforcement and regulators** in response to lawful requests (search warrants, court orders, etc.). We require valid legal process; we challenge overbroad requests where appropriate. - **Acquirers** in the event of a merger, acquisition, or sale of assets, subject to a confidentiality undertaking. - **Other partners on shared sending infrastructure** — only reputation-affecting metadata (suppression list entries are account-private; reputation scores are aggregated such that no single partner is identifiable). We do **not** sell your personal information. ## 5. International transfers We are headquartered in India and primary storage is in India. Some Sub-processors are located outside India (analytics, error monitoring). For transfers from the EU/EEA, we rely on Standard Contractual Clauses with supplementary measures. For DPDP Act compliance, we comply with any government notifications restricting transfers to specific jurisdictions. ## 6. Retention | Category | Retention | |---|---| | Account profile | Term of agreement + 3 years (DPDP §11(3)) | | Billing and tax records | 8 years (Income Tax Act) | | Authentication logs | 12 months | | Support records | 5 years | | Marketing-consent records | Until consent withdrawn + 3 years | | Aggregated analytics | Indefinite (no longer personally identifiable) | After retention periods expire, we delete personal data in a documented, audited process. Backup copies cycle out within 90 days. ## 7. Your rights Under the **DPDP Act, 2023**, you have the right to: - **Access** the personal data we hold about you and the names of Data Processors we have shared it with. - **Correct** inaccurate or incomplete data. - **Erase** data we no longer have a lawful basis to keep. - **Withdraw consent** for any processing we rely on consent for. - **Nominate** another person to exercise your rights in case of death or incapacity. - **Grievance redressal** — escalate through our Grievance Officer to the Data Protection Board of India. Under the **EU GDPR**, you additionally have the right to: - **Data portability** — receive a copy in machine-readable format. - **Object** to processing based on legitimate interest. - **Lodge a complaint** with your supervisory authority (e.g. CNIL, ICO, Garante). To exercise rights, email **dpo@splashifypro.in**. We respond within **30 days** (DPDP) or **1 month, extendable by 2 months for complex requests** (GDPR), at no cost. We may need to verify your identity. ## 8. Cookies and tracking The Partner Panel uses: - **Strictly necessary cookies** — session, CSRF token, login state. These are not subject to consent. - **Functional cookies** — UI preferences. Set on first interaction. - **Analytics** — aggregated usage to improve the panel. We do not use third-party advertising trackers in the Partner Panel. You can manage cookies via your browser settings. Disabling strictly necessary cookies will prevent the panel from working. The marketing site (splashifypro.com) uses additional analytics and advertising trackers; see the [marketing-site Privacy Policy](https://splashifypro.com/privacy-policy) for details. ## 9. Security See [DPA §9](/legal/data-processing-agreement#9-security-measures) for the technical and organizational measures we implement. In summary: encryption in transit and at rest, role-based access control, multi-factor authentication for staff, secure development practices, periodic penetration testing, and incident response. No system is perfectly secure. If you suspect a security issue, email **security@splashifypro.in**. ## 10. Children The Service is for businesses and not directed at individuals under 18. We do not knowingly collect data from minors. If you believe a minor has provided data, email **dpo@splashifypro.in** and we will delete it. ## 11. Automated decision-making The [Auto-Action System](/legal/auto-actions) automatically restricts or pauses accounts that breach reputation or AUP thresholds. These decisions are based on objective metrics (bounce rate, complaint rate, content classifier output) and are reviewable on appeal. The system does not profile partners for any purpose other than preventing service abuse. ## 12. Marketing We may use your account email to send: - **Service notifications** (incidents, scheduled maintenance, product updates) — you cannot opt out as long as you have an active account. - **Marketing emails** about new features, integrations, and offers — opt-in only at signup; unsubscribe anytime. We will never sell your contact details to third parties for marketing. ## 13. Grievance redressal Under Rule 3 of the IT (Intermediary Guidelines and Digital Media Ethics Code) Rules, 2021, we have appointed a Grievance Officer: - **Name:** Grievance Officer, Splashify Pro - **Email:** grievance@splashifypro.in - **Address:** Shimultala, Motiganj, Bongaon, North 24 Parganas, West Bengal — 743235, India The Grievance Officer acknowledges complaints within **24 hours** and resolves within **15 days**. If you are dissatisfied with the Grievance Officer's decision, you may escalate to the **Grievance Appellate Committee** under Rule 3A. ## 14. Changes We may update this Privacy Policy. We will notify you of material changes via email and a banner in the Partner Panel at least **30 days** before the change takes effect. Continued use after the effective date is acceptance. ## 15. Contact | For | Email | |---|---| | Privacy questions, rights requests | dpo@splashifypro.in | | Grievance redressal | grievance@splashifypro.in | | Security incidents | security@splashifypro.in | | Account or billing | support@splashifypro.in | Postal address: Shimultala, Motiganj, Bongaon, North 24 Parganas, West Bengal — 743235, India. ======================================================================== ## Service Level Agreement URL: https://partner-docs.splashifypro.com/legal/service-level-agreement ======================================================================== # Service Level Agreement **Effective:** 1 May 2026 This Service Level Agreement ("SLA") sets out the availability and support commitments for the Splashify Pro Email API. It applies to production accounts on a paid plan; sandbox and trial accounts are provided on a best-effort basis without SLA commitments. ## 1. Definitions - **Available** — The Email API endpoint at `api.splashifypro.com` accepts a well-formed authenticated request and responds within 30 seconds with HTTP status `2xx` for valid input or appropriate client error (`4xx`) for invalid input. - **Unavailable** — Any 5-minute interval in which the API returns `5xx` errors for more than 50% of valid requests, or fails to respond within 30 seconds for more than 50% of valid requests. - **Monthly Uptime** — `(Total minutes in month - Unavailable minutes) ÷ Total minutes in month`, expressed as a percentage. - **Excluded Downtime** — See §4. ## 2. Uptime commitment | Surface | Monthly Uptime target | |---|---| | Email API (`/partner/email/*`) | **99.95%** | | Webhook delivery (egress) | **99.9%** | | Partner Panel (`partner.splashifypro.com`) | **99.9%** | | Partner Documentation (`partner-docs.splashifypro.com`) | **99.9%** | 99.95% allows for ~22 minutes of unavailability per 30-day month. 99.9% allows for ~43 minutes. Uptime is measured from publicly-reachable monitoring nodes in at least 3 geographic regions (Mumbai, Singapore, Frankfurt). The authoritative measurement is published at [status.splashifypro.com](https://status.splashifypro.com). ## 3. Service credits If we miss the uptime target in a calendar month, you are entitled to service credits applied to the next month's invoice: | Monthly Uptime achieved | Credit | |---|---| | < 99.95% and ≥ 99.9% | 5% of monthly fees for the affected service | | < 99.9% and ≥ 99.0% | 10% | | < 99.0% and ≥ 95.0% | 25% | | < 95.0% | 50% | ### Claiming credits 1. Submit a credit request to **billing@splashifypro.in** within **30 days** of the affected month. 2. Include account ID, the dates and times of the outage you observed, and the impact on your sending. 3. We respond within 10 business days. Credits are applied to the next invoice. They cannot be exchanged for cash and do not roll over more than 12 months. Credits are your **sole and exclusive remedy** for missed uptime targets, except in cases of gross negligence or wilful misconduct. ## 4. Excluded downtime The following do not count against the uptime target: - **Scheduled maintenance** announced at least 7 days in advance via the status page and partner email. We schedule maintenance during low-traffic windows where possible. - **Force majeure** events outside our reasonable control — earthquakes, floods, war, terrorism, civil unrest, labor strikes, internet backbone outages, government action. - **Recipient mailbox provider issues** — slow or rejected delivery caused by the recipient's mail server. Webhook events for these outcomes are still delivered. - **Customer-caused outages** — your account paused for AUP violations, missing payment, exhausted wallet, exceeded quotas, or insufficient sender reputation. - **Your network or DNS issues** — failure of your DNS records, your firewall blocking our IPs, or your network infrastructure. - **API misuse** — sending malformed requests, exceeding rate limits, or other behavior counted as `4xx` (which by definition is not an outage on our side). - **Beta features** — anything labelled "Beta" in the documentation. ## 5. Support response times Support is provided in English via email and the in-product chat during Indian business hours (Mon–Sat, 09:30–19:30 IST). | Severity | Definition | First response | Update cadence | |---|---|---|---| | **S1 – Critical** | Production sending blocked, security incident | 1 business hour | Every 2 business hours | | **S2 – High** | Major feature broken, significant deliverability impact | 4 business hours | Every business day | | **S3 – Medium** | Single feature broken, workaround exists | 1 business day | Every 2 business days | | **S4 – Low** | Question, feature request, documentation issue | 2 business days | As needed | S1 issues outside business hours are picked up by the on-call rotation; first response within 4 hours. Severity is set by you when you open the ticket. We may downgrade severity if the issue does not match the definitions above. ## 6. Webhook delivery commitment When a deliverable event (`Send`, `Delivery`, `Bounce`, `Complaint`, `Open`, `Click`, etc.) is generated for a sender that has configured a webhook destination on its [configuration set](/api-reference/configuration-sets/list): - We will attempt delivery to the configured URL. - We will retry on `5xx` and timeout responses according to [our retry schedule](/webhooks/retries). - We will not retry on `2xx` (delivered) or `4xx` (rejected by recipient — assumed to be deliberate). - Webhook backlogs older than **24 hours** that we have failed to deliver after the documented retries are dropped, with audit-log entries you can query. 99.9% target on webhook delivery means we deliver at least 99.9% of generated events within the documented retry window. Events that your endpoint returns `4xx` for are counted as delivered for SLA purposes (we did our part). ## 7. Deliverability We do **not** offer a deliverability SLA. Deliverability depends on your sender reputation, content, list quality, and the policies of recipient mailbox providers — none of which we control. We provide the tooling to maintain good deliverability: [verified identities](/api-reference/identities/list), [suppression list](/api-reference/suppression/list), [reputation monitoring](/api-reference/quotas/get-reputation), [event webhooks](/webhooks/event-types). If you suspect a deliverability issue caused by Splashify Pro infrastructure (e.g. our shared sending IP is on a blocklist), open an S2 ticket and we will investigate. ## 8. Data durability For data we store on your behalf (suppression list, configuration sets, templates, audit log), we target **99.999999999% (11 nines)** durability over a calendar year. We achieve this through replicated storage with periodic backups; no customer action is required. Email **content** in the outbox is retained for 30 days per the [DPA §7](/legal/data-processing-agreement#7-retention) and is not backed up beyond that window. ## 9. Termination for repeated SLA breaches If we miss the monthly uptime target in **3 consecutive months** or in any **5 of 12 rolling months**, you may terminate the affected service at the end of the current billing period without penalty. Pro-rated refund of pre-paid fees is available on request. ## 10. Changes to this SLA We may update this SLA with **60 days' prior notice**. Changes that make the SLA materially less protective give you the right to terminate without penalty within 30 days of the change taking effect. Improvements take effect immediately without notice. ## Status and incident communication | Channel | What it's for | |---|---| | [status.splashifypro.com](https://status.splashifypro.com) | Real-time component status, scheduled maintenance, post-mortems | | Email to account-admin | S1 incidents, scheduled maintenance | | Partner panel banner | All incidents that affect the partner panel | | `support@splashifypro.in` | Open a ticket | | `security@splashifypro.in` | Security incidents, breach notifications | ======================================================================== ## API Terms of Service URL: https://partner-docs.splashifypro.com/legal/terms-of-service ======================================================================== # API Terms of Service **Effective:** 1 May 2026 These API Terms of Service ("Terms") form a binding contract between you ("you", "your", "Partner") and **EvolvePro Tech Solutions Private Limited** ("Splashify Pro", "we", "us", "our"), CIN U62012WB2025OPC281483, registered at Shimultala, Motiganj, Bongaon, North 24 Parganas, West Bengal — 743235, India. By creating an account, generating an API key, or sending a single request to the Splashify Pro Email API, you agree to these Terms. ## 1. The service The Splashify Pro Email API ("Service") provides programmatic email sending, identity verification, suppression management, and delivery analytics through endpoints documented at **partner-docs.splashifypro.com**. ## 2. Eligibility You may use the Service only if: - You are at least 18 years old. - You have the legal authority to enter into this agreement on behalf of yourself or the entity you represent. - You are not located in, ordinarily resident in, or organized under the laws of a country subject to comprehensive Indian or US trade sanctions. - You have not previously been terminated by us for breach. ## 3. Account registration You will provide accurate, current, complete information during registration and keep it up to date. You are responsible for safeguarding your API keys and for all activity under your account. Notify **security@splashifypro.in** immediately if you suspect unauthorized access. You are responsible for the actions of every team member or end user you grant access to. ## 4. Acceptable use You must comply with the [Acceptable Use Policy](/legal/acceptable-use-policy) and the [Anti-Spam Policy](/legal/anti-spam) at all times. Breach triggers the [Auto-Action System](/legal/auto-actions) and may result in account termination. ## 5. Fees and payment ### 5.1 Pricing Email sending is billed at **₹0.01 per message**, with the first **200 emails per 24 hours free** while in sandbox. Production accounts pay the per-email rate plus any minimum-commit pricing in your order form. Pricing is in Indian Rupees (INR) and exclusive of GST and other applicable taxes. ### 5.2 Wallet model Production accounts maintain a prepaid wallet balance. Sends are deducted from the wallet at the time of acceptance. Insufficient wallet balance results in send rejection until the wallet is topped up. Wallet recharges are processed via Zoho Payments and a tax invoice is issued automatically. GST is applied at the rate prescribed by Indian tax law (currently 18% IGST or CGST+SGST). ### 5.3 Refunds Wallet balance refunds are governed by the [Refund Policy](https://splashifypro.com/refund-policy). In summary: - Used balance is non-refundable. - Unused balance can be refunded on account closure, less applicable GST, within 7 working days. - Per-email charges that fail due to verifiable Splashify Pro infrastructure issues are credited back automatically. ### 5.4 Late payment Invoices issued for post-paid services are due within 30 days. Late payment accrues interest at 1.5% per month (18% per annum). We may suspend the Service after 60 days' overdue and terminate after 90 days. ### 5.5 Taxes You are responsible for all taxes assessed on your use of the Service except taxes on our income. For Indian customers, GST is included on every invoice; for foreign customers, the export of service is zero-rated under Indian GST law. ## 6. Your data You retain all rights, title, and interest in the data you send through the Service ("Customer Data"), including email content, recipient lists, and template content. You grant us a limited, worldwide, royalty-free license to process Customer Data solely for the purpose of operating the Service — delivering email, generating delivery analytics, providing support, detecting abuse, and complying with law. The processing of Customer Data is governed by the [Data Processing Agreement](/legal/data-processing-agreement). ## 7. Our intellectual property We retain all rights, title, and interest in the Service, including the API, documentation, software, designs, trademarks, and copyrighted material. You receive a limited, non-exclusive, non-transferable, revocable license to use the Service per these Terms. You may not: - Reverse-engineer, decompile, or attempt to derive the source code of any part of the Service except as permitted by mandatory law. - Remove or alter our trademarks, copyright notices, or attribution. - Use the Service to build a competing service. - Use scraping, mass-extraction, or "scraper" tools against any endpoint. ## 8. Confidentiality Each party will protect the other's confidential information using the same care it uses for its own (and at least reasonable care). Confidential information includes pricing, technical specifications, and any information marked or reasonably understood as confidential. Our system telemetry, internal architecture, and operational details are confidential to us. ## 9. Service Level Agreement We commit to the uptime and support response times in the [Service Level Agreement](/legal/service-level-agreement). Service credits are your sole remedy for missed uptime targets. ## 10. Suspension and termination ### 10.1 By you You may cancel your account at any time via the Partner Panel or by emailing **support@splashifypro.in**. Cancellation takes effect at the end of the current billing period. Unused wallet balance is refunded per §5.3. ### 10.2 By us We may suspend or terminate your account immediately, without notice, if: - You materially breach these Terms or any of the linked policies. - The [Auto-Action System](/legal/auto-actions) detects severe abuse. - Required by law or court order. - Your account becomes inactive for 12 consecutive months and has zero wallet balance. For non-severe breaches, we will give you a 7-day cure period if remediation is feasible. We may also terminate for convenience with **90 days' written notice**. ### 10.3 Effect of termination On termination: - Your access to the API is revoked immediately. - We delete email content and delivery metadata per the retention schedule in the [DPA §7](/legal/data-processing-agreement#7-retention). - Suppression-list entries are retained per legal obligation. - Outstanding invoices remain due. Pre-paid wallet balance is refunded per §5.3 unless termination was for severe breach (in which case it may be retained to cover damages). - Sections 5 (for unpaid amounts), 6, 7, 8, 11, 12, 13, 14, 15, and 16 survive termination. ## 11. Warranties and disclaimers You warrant that: - You have all rights and consents necessary for the email content and recipient lists you send through the Service. - You will comply with all applicable laws. - You will not use the Service to send the content prohibited by the AUP. The Service is provided **"as is" and "as available"**. We disclaim all implied warranties to the maximum extent permitted by law, including implied warranties of merchantability, fitness for a particular purpose, non-infringement, and any warranties arising from a course of dealing or usage of trade. We do not warrant that the Service will be uninterrupted or error-free or that any particular email will be delivered to a recipient's inbox. ## 12. Indemnity You will defend, indemnify, and hold harmless Splashify Pro and its officers, directors, employees, and Sub-processors from and against all third-party claims, damages, liabilities, settlements, and expenses (including reasonable attorneys' fees) arising out of or related to: - Your breach of the AUP, Anti-Spam Policy, or these Terms. - Customer Data, including claims that it infringes third-party rights or violates law. - Your products or services. We will defend you against any claim by a third party that your authorized use of the Service infringes that party's copyright, trade secret, or registered Indian patent, subject to the cap in §13. ## 13. Limitation of liability To the maximum extent permitted by law: - Neither party will be liable for indirect, incidental, special, consequential, exemplary, or punitive damages, or for lost profits, lost revenue, lost data, or business interruption, arising out of these Terms, even if advised of the possibility. - Each party's total cumulative liability arising out of or related to these Terms is capped at the **fees paid to Splashify Pro by you in the 12 months preceding the claim**. - The cap does **not** apply to: (a) your obligations under §5 (Fees), (b) your indemnity under §12, (c) breach of confidentiality under §8, (d) infringement of the other party's intellectual property, (e) gross negligence or wilful misconduct, (f) statutory liabilities that cannot be limited by contract under Indian or EU data-protection law. ## 14. Force majeure Neither party is liable for delay or failure caused by events outside its reasonable control, including acts of God, government action, war, terrorism, riots, internet backbone failures, or recipient mailbox provider blocking. The affected party will resume performance as soon as reasonably practicable. ## 15. Governing law and dispute resolution These Terms are governed by the laws of India. Disputes will first be referred to good-faith negotiation between designated representatives of each party for **30 days**. If unresolved, disputes will be referred to **mediation** under the Mediation Act, 2023 conducted by a sole mediator agreed by the parties or, failing agreement, appointed by the Indian Council for Mediation. If mediation fails, disputes will be finally resolved by **arbitration** in **Kolkata, West Bengal** under the Arbitration and Conciliation Act, 1996, by a sole arbitrator agreed by the parties or appointed by the Calcutta High Court. The seat is Kolkata; the language is English. The arbitral award is final and binding. The courts of Kolkata have exclusive jurisdiction for matters not subject to arbitration (including interim relief). Nothing prevents either party from seeking equitable relief in any court of competent jurisdiction to protect intellectual property or confidential information. ## 16. General - **Notices.** Notices to you may be given by email to your account email or by posting in the Partner Panel. Notices to us must be sent to **legal@splashifypro.in** with a copy to our registered office address. - **Assignment.** You may not assign these Terms without our prior written consent. We may assign these Terms in connection with a merger, acquisition, or sale of substantially all of our assets, on notice to you. - **Entire agreement.** These Terms, the linked policies, and any signed order form constitute the entire agreement between the parties. They supersede all prior agreements. - **No partnership.** Nothing in these Terms creates a partnership, joint venture, agency, or employment relationship. - **Severability.** If any provision is held unenforceable, the remainder continues in full effect. - **No waiver.** Failure to enforce a provision is not a waiver. - **Modifications.** We may update these Terms with 30 days' notice for material changes; updates for clarity or new features take effect immediately. Continued use after the effective date is acceptance. - **Headings.** Headings are for convenience only. - **Counterparts and electronic acceptance.** These Terms may be accepted electronically. An electronic acceptance has the same effect as a wet-ink signature under the IT Act, 2000. ## Contact | For | Email | |---|---| | Sales / contracts | sales@splashifypro.in | | Support | support@splashifypro.in | | Billing | billing@splashifypro.in | | Legal | legal@splashifypro.in | | Grievance Officer | grievance@splashifypro.in | | Data Protection Officer | dpo@splashifypro.in | | Abuse | abuse@splashifypro.in | | Security | security@splashifypro.in |