
Developer Documentation
Integrate SplashSend into your application with our REST API. Add people, manage subscriptions and start drips from your own code.
Build an app with your AI
SplashSend writes a brief for the AI tool you build with. Make an app for your own account, or for every SplashSend account.
- Describe itSay what the app should do, and tick what it needs, such as adding people or sending events.
- Build it with your AICopy its brief into your AI tool. The brief says what to build, how to reach SplashSend and how to keep the key safe.
- Switch it onIt starts in test mode, where nothing it does sends an email. When it works, switch it to live.
Two kinds of app
For your account, or for everyone
Just for your account
For your own shop, website or booking system. It’s ready as soon as you make it, and only your account can use it.
- A key, shown once, that you keep in your AI tool as a secret
- No review, and nothing to publish
- Free on every plan
For every SplashSend account
For software that many businesses use. Each account connects through SplashSend’s allow screen and agrees to what it asks for.
- Connects with OAuth, so accounts never share a key
- Checked by us, then listed in Apps
- Free to install on every plan
Everything your AI needs
Docs written for AI tools to read
A guide written for AI tools
Every endpoint, permission and error, in one plain text file your AI can read.
The API reference
In OpenAPI 3.1, for tools that read it, with a page to read yourself.
SplashSend docs in your editor
Add our docs to your AI code editor, and your AI can look up an endpoint and check a request before it sends it.
Test mode
A new app can add people and send events, but nothing it does starts a drip or sends an email until you switch it to live.
Switch to live, on the app’s page
Start from an example
Briefs for the apps people make most
Each one is a starting point. Make an app from it, and its brief is filled in with your app’s details.
Add people from your website's form
When someone signs up on a form you built, add them to SplashSend with what they told you.
Send bookings as events
When someone books, send an event a drip can start from, such as a reminder the day before.
Keep your products up to date
Send your products in with their prices and pictures, so your emails can show them.
Send sales from your till
Send each sale in as an order, so Reports can tell you which emails brought people in.
Want every SplashSend account to have it?
Make it as a public app and send it to us. Once we’ve checked it, it’s listed in Apps, free to install, with your name on it.
Authentication
All API requests need an API key, sent in the Authorization header as a Bearer token. Create keys under Setup → API keys. When you create a key you choose what it can do: a new key can read and add people and events, and you can tick more, such as orders, products, lists or data sets. A request the key isn’t allowed to make gets a 403.
curl https://splashsend.com/api/v1/contacts \
-H "Authorization: Bearer sk_live_your_api_key_here"bashKeep your API keys safe
Never expose API keys in client-side code. Always make API calls from your server. Keys are shown only once at creation and cannot be retrieved later.
Connecting a public app
A public app connects to each SplashSend account with OAuth 2.1 and PKCE. The account sees what the app asks for on SplashSend’s allow screen, and the app gets a token that works for that account only. Make the app under Apps, then Apps you’ve made, to get its client ID and secret.
- Send the person to https://splashsend.com/oauth/authorize with response_type=code, your client_id, a redirect_uri you registered, the scope (the permissions it needs, separated by spaces), a state, a code_challenge and code_challenge_method=S256.
- They choose Allow and come back to your redirect_uri with a code and the state. Check that the state is the one you sent.
- Swap the code at https://splashsend.com/api/oauth/token, form-encoded, with grant_type=authorization_code, the code, the redirect_uri and the code_verifier. Send the client ID and secret as HTTP Basic.
- Send the access token with each request as Authorization: Bearer, the same way a key is sent. It lasts an hour. Before then, swap the refresh token for new ones with grant_type=refresh_token. Each refresh token works once.
- A 401 means the account removed your app. Stop calling for that account and ask them to connect again.
curl -X POST https://splashsend.com/api/oauth/token \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d grant_type=authorization_code \
-d code="$CODE" \
-d redirect_uri=https://yourapp.example/splashsend/callback \
-d code_verifier="$CODE_VERIFIER"bashWhat a public app can’t do
Whatever it asks for, a public app can’t subscribe someone who unsubscribed. It sends an email only if it asks to send campaigns and the account allows it, and each send goes through the same checks as the Send button. Tools that read OAuth metadata can find these addresses at https://splashsend.com/.well-known/oauth-authorization-server.
Base URL
All API endpoints are relative to:
https://splashsend.com/api/v1textPeople
Create, list, update, and manage your people.
Every endpoint in one place
It covers people, lists, imports, orders, events, products and your data sets. Each endpoint is listed with its fields, what it returns and an example to copy, from the same description the API publishes at /api/openapi.
Open the API referenceWebhooks
Receive real-time notifications when events happen in your account. Configure webhook endpoints in your dashboard under Setup → Webhooks.
Secure by default
Every webhook request is signed with HMAC-SHA256 using a secret key unique to each endpoint. Always verify the signature before processing payloads.
Event types
| Event | Description |
|---|---|
| contact.subscribed | Someone joins now, through a form, the API, an AI assistant, the app or your Shopify store. It isn't sent for people brought in by an import or your store's first sync, unless the import was set to start automations. |
| contact.unsubscribed | A person unsubscribes from your emails, or a spam complaint unsubscribes them |
| contact.updated | A person's name, email address, source or details change. It's sent only when something changed, and data.changed lists what. |
| campaign.sent | A campaign finishes sending to all recipients |
| email.delivered | An email is successfully delivered to the recipient |
| email.opened | A recipient opens an email |
| email.clicked | A recipient clicks a link in an email |
| email.bounced | An email bounces (hard or soft) |
| email.complained | A recipient marks your email as spam |
| order.placed | A new order arrives from your Shopify store or the API, dated in the last 3 days |
| order.fulfilled | An order is marked shipped or fulfilled |
| submission.created | Someone submits an answer to a Collect form (a review, survey or feedback) |
| drip.enrolled | A person starts a drip |
| drip.step | A drip reaches a “Notify your systems” step |
| form.submitted | A form’s “Send it to another tool” action runs after someone submits it |
Payload format
Each webhook POST request includes a JSON body and two special headers:
| Header | Description |
|---|---|
| X-SplashSend-Event | The event type (e.g. contact.subscribed) |
| X-SplashSend-Signature | HMAC-SHA256 hex digest of the raw request body |
{
"id": "evt_Q2xh5M8d0Tq1pZ7kVbN3yR6s",
"event": "contact.subscribed",
"timestamp": "2026-10-09T14:30:00.000Z",
"data": {
"contactId": "cm4u2x8yq0003l208f1k9d7vz",
"email": "jane@example.com",
"name": "Jane Doe"
}
}jsonVerifying signatures
To verify a webhook is authentic, compute the HMAC-SHA256 of the raw request body using your webhook secret and compare it to the signature header.
# Save the request body exactly as it arrived, for example as body.json,
# and put your endpoint's signing secret in WEBHOOK_SECRET.
WEBHOOK_SECRET='your_webhook_secret'
# The signature to expect: the hex HMAC-SHA256 of the raw body.
openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" < body.json | awk '{print $NF}'
# It must equal the X-SplashSend-Signature header exactly.curlDelivery & retries
Reply with any 2xx status to acknowledge a message. Each message is tried up to three times: the second try comes 5 seconds after the first fails, and the third 15 seconds after the second. Each try waits up to 10 seconds for a reply. Redirects aren't followed, so a 3xx reply counts as a failure. Every try of a message has the same id, so you can skip one you've already handled. If every try fails for 20 messages in a row, and the first of those failures was at least a day ago, the endpoint is paused and the account's owner and admins are emailed. Under Setup → Webhooks, an endpoint's Deliveries log shows its latest 50 tries. From there you can Redeliver one message, or click Send failed ones again to resend everything from the last 30 days that didn't get through.
Error handling
All errors return a consistent JSON envelope with an error code and human-readable message.
{
"error": {
"code": "validation_error",
"message": "email is required and must be a string"
}
}json| Status | Code | Description |
|---|---|---|
| 400 | validation_error | Request body is invalid or missing required fields |
| 400 | invalid_request | The body isn't valid JSON, or isn't the shape the endpoint expects. |
| 400 | read_only | The data set is built in or read from a connected source, so it can't be changed this way. |
| 400 | limit_reached | A cap was reached, such as 5,000 rows in a data set, 100 data sets or 500 lists in an account. |
| 401 | unauthorized | API key is missing, invalid, or revoked |
| 403 | forbidden | The API key isn’t allowed to do this. Give it the permission under Setup → API keys. |
| 403 | plan_limit_reached | You've reached the people limit on your current plan. Upgrade to add more. |
| 404 | not_found | The requested resource does not exist |
| 409 | stopped_address | The address is stopped because it bounced or was reported as spam, so it can't be subscribed. |
| 409 | idempotency_in_progress | A request with this Idempotency-Key is still running. Wait for the Retry-After header (5 seconds), then retry. |
| 413 | payload_too_large | The request body is over the size limit: 256KB, or 1MB for an import and 4MB for data-set rows. |
| 422 | idempotency_key_reused | This Idempotency-Key was already used for a different request. Send a new key. |
| 429 | rate_limited | Too many requests. Wait for the number of seconds in the Retry-After header, then try again. |
| 500 | internal_error | Something went wrong on our end |
Rate limiting
Each API key can make 100 requests a minute. A request over the limit gets a 429 with the code rate_limited and a Retry-After header of 60 seconds. Requests refused because the key is missing a scope count towards the limit, and so do an AI assistant's requests made with the same key.
Need higher limits?
Contact us at hello@splashsend.com to discuss your requirements.