Custom HTTP integration
Connect a custom HTTP delivery provider to FounderHQ Sequences.
Send through any provider with an HTTP API, by describing the request FounderHQ should make. About 20 minutes with the provider's API docs open.
Before you start
Check Sequences providers first — if your provider already ships as a recipe, use that instead and skip this page. You need the provider's send-endpoint documentation and an API credential. Then open Sequences in FounderHQ and add an HTTP integration.
How it works
You define one HTTP integration: a request template plus how to read the response. You then create a channel connection that points at it and picks the channel — email, SMS, WhatsApp, or push.
For every message the sequence sends, FounderHQ fills your template with that message's data, calls your endpoint, and reads the reply to learn whether it worked.
Build the request
Method — GET, POST, PUT, PATCH, or DELETE.
URL — must be a public http or https address. It can contain
variables.
Headers and query parameters — plain key/value pairs. Values can contain variables.
Body — pick one of three modes:
| Mode | What you write | When to use it |
|---|---|---|
json | A JSON structure. Variables run inside string values. | Most JSON APIs |
form | Field names and values. Each value is URL-encoded after it renders. | Form-encoded APIs. You never need escaping filters. |
raw | One string, sent exactly as it renders. | Anything else, or when you need a conditional around whole branches of JSON |
Templating uses Liquid. The include, render, and layout tags are
turned off.
{ "to": {{ recipient.phone | json }},
"text": {{ message.text | json }},
"callback": {{ channel.statusCallbackUrl | json }} }The json filter is worth using on every value in raw mode. It quotes
and escapes the value, so an apostrophe in a message body cannot break your
JSON.
A variable with no value renders as an empty string rather than failing — with one exception, described under Guards.
Variables you can use
The person
| Variable | Value |
|---|---|
recipient.value | Whatever address this channel sends to |
recipient.email | Email channel only |
recipient.phone | SMS and WhatsApp channels |
recipient.token | Push channel — the device token |
recipient.externalId | Your own ID for the contact, when the provider resolves people itself |
The message
| Variable | Value |
|---|---|
message.outboundMessageId | FounderHQ's ID for this send. Pass it through if the provider echoes a reference back. |
message.contactId | The contact this send belongs to |
message.subject | Email subject |
message.text | Plain-text body |
message.html | HTML body |
message.title | Push title |
message.body | Push body |
message.templateName | The approved template name, on template sends |
message.senderId | India DLT sender header, from the SMS template |
message.dltTemplateId | India DLT template ID, from the SMS template |
message.smsType | India DLT category |
message.payload | The whole rendered payload, for anything not listed above |
The connection
| Variable | Value |
|---|---|
channel.type | EMAIL, SMS, WHATSAPP, or PUSH |
channel.providerType | The provider kind behind this connection |
channel.connectionId | This connection's ID |
channel.statusCallbackUrl | A delivery-report address unique to this connection, with its own token |
WhatsApp template sends also get message.metaTemplate (the whole
Cloud API template object, for providers that pass Meta's format through),
message.bodyParamTexts (the body variables in order), and
message.bodyParamMap (the same values keyed by name or position).
Authenticate
You store exactly one secret per integration. Pick how it is sent:
| Type | What FounderHQ sends |
|---|---|
none | Nothing |
bearer | Authorization: Bearer <secret> |
basic | Authorization: Basic <base64 of username:secret> |
custom_header | A header you name, with an optional literal prefix before the secret — for example Basic for a key you already encoded |
body_field | The secret as a top-level field of the JSON body, under a name you choose |
hmac_sha256 | An HMAC-SHA256 of the request body, signed with the secret, in a header you name. Defaults to x-founderhq-signature and a sha256= prefix. |
The secret is applied after the template renders, so it never appears in anything FounderHQ stores or shows you.
Read the response
Point FounderHQ at the fields it should read, using dotted paths like
data.id or messages.0.id.
| Setting | What it finds |
|---|---|
idPath | The provider's message ID. Delivery reports match against it. Falls back to a top-level id. |
urlPath | A link to the message at the provider. Falls back to a top-level url. |
statusPath | A status value in the body. Falls back to a top-level status. |
errorPath | The error text to show you when the send fails. Falls back to error, then message. |
failStatus | A status value that means failure even on an HTTP 200. Set this for APIs that answer 200 with {"type":"error"}. |
FounderHQ retries on 429 and on 5xx responses. Every other failure is permanent — a bad key or a rejected recipient stops rather than looping. Requests time out after 15 seconds.
Delivery reports
channel.statusCallbackUrl is a per-connection address carrying its own
token. Two ways to use it:
- Put it in the send request, if the provider takes a callback URL per message. Nothing to configure at the provider.
- Paste it into the provider's dashboard, if that is where webhooks are set.
FounderHQ understands the Meta WhatsApp envelope and the delivery-receipt formats of the SMS providers it ships recipes for. A provider with a shape of its own can POST FounderHQ's normalized status format instead.
If you wire no callback at all the integration still works. Messages just stop at Sent, and sequence steps cannot wait on delivery.
Guards
FounderHQ refuses to send a request it knows is wrong:
- No recipient. Your template must reference the right recipient variable for the channel, and it must have a value. Otherwise the send fails instead of going out addressed to nobody.
- Empty regulatory fields. If your template references an India DLT variable, that variable must have a value. It is never sent blank. See India DLT.
- Session messages the provider cannot take. A connection marked as template-only rejects free-text WhatsApp sends outright.
Verify
- Use the integration's test send. It shows the exact request FounderHQ built and the provider's raw reply.
- The reply's message ID appears in the mapped result. If it reads empty,
fix
idPath. - Build a sequence with one step on this connection and run yourself through it. The message arrives, and the sequence shows it as Sent.
Troubleshoot
The test send says a required variable is missing
Your template references something the test context has no value for. Check the variable names against the tables above.
The provider answers 200 but nothing arrives
The API is reporting failure inside a successful response. Set statusPath
and failStatus so FounderHQ treats it as a failure too.
Messages send but never reach Delivered
No callback is wired. Either add channel.statusCallbackUrl to the send
request, or paste it into the provider's webhook settings.
The body is malformed when a message contains quotes
You are in raw mode without the json filter. Write
{{ message.text | json }}, not "{{ message.text }}".