Whenever Subcontext calls your server (an agent tool, a webhook, Send to an integration, a policy sync), the rules on this page apply.
Addresses#
- HTTPS only. Addresses must start with
https://. Plainhttp://is refused before anything is sent. - Public addresses only. Subcontext won't call
localhost, private network addresses (such as10.x.x.xor192.168.x.x), link-local addresses, or names ending.localor.internal. A hostname must resolve only to public addresses, and the request goes to the address that was checked. - Any port is fine:
https://crm.example.com:8443/hooksworks. - Redirects aren't followed. A
301,302or307counts as a refusal (see below). Give Subcontext the final address. - Allow-lists. If your firewall only lets in known addresses, email hello@subcontext.com for the addresses Subcontext's requests come from. Don't rely on the address alone. Always check the credential or signature too.
Timeouts#
| Step | Limit |
|---|---|
| Opening the connection | 5 seconds |
| Waiting for your answer | 15 seconds |
Answer quickly and do slow work afterwards (put it on a queue). A webhook
receiver should answer 200 as soon as it has checked the signature and stored
the event.
How your answer is read#
| Your status | What Subcontext takes it to mean | What happens next |
|---|---|---|
200 to 299 |
Success. | Done. The body is optional for webhooks; for agent tools and custom endpoints it's what the agent or the success conditions read. |
401 from an API that Subcontext sends a credential to (an API key, bearer token, basic auth or OAuth2) |
The credentials are wrong. | Not retried (with OAuth2 client credentials, Subcontext first gets a new token and tries once more). The connection is marked credentials rejected and stops sending until someone saves new credentials. |
401 from a webhook receiver |
This delivery was refused. A webhook has no credential to reject, only its signature. | Treated like any other refusal (next row). Later deliveries still go out. |
429, 500 to 599, a timeout, or no connection |
A temporary problem on your side. | Retried later (see Retries). |
Anything else: 3xx, 400, 403, 404, 409, 422... |
Your system refused this request. | Not retried: sending the same request again would get the same answer. Recorded as failed. |
When you choose a status:
- On an API, use
401only for "these credentials are wrong", never for a bad body. Use400for that. - A webhook receiver can answer a bad signature with
400,401or403. Each refuses only that one delivery. - Use
429or503when you want Subcontext to come back later. - Use
4xxwith a short JSON message when the request itself is wrong. An agent sees the message (see What the agent sees).
A success body doesn't have to be JSON. Anything that isn't is kept as text.
Retries#
| What's being sent | Retried? |
|---|---|
| Webhooks (Send a webhook, and lifecycle webhooks) | Up to 5 attempts in all, waiting longer each time (roughly 3 seconds, 20 seconds, 1.5 minutes, then 4 minutes). |
| Send to an integration (workflows) | The same: up to 5 attempts. |
| Agent tool calls | Not automatically. The agent is told the system is temporarily unavailable and offers the customer another try. |
| Conversions to Google Ads and Meta | Up to 5 attempts. |
After the last attempt the send is recorded as failed. For workflows, a
manager can press Retry on the workflow's page to send the last 7 days'
failures again, once your side is fixed. Retries carry the same
Idempotency-Key, so your side can recognise them.
GET, PUT and DELETE requests can also be repeated once straight away if the connection drops before your answer arrives, so make those safe to repeat.
Duplicate protection: the Idempotency-Key header#
Every POST, PUT or PATCH that Subcontext sends automatically (webhooks,
workflow sends, policy sync, agent tool writes) carries an Idempotency-Key
header. It's the same on every retry of the same send, and different for
different sends. It's an opaque 64-character hex string that says nothing
about what was sent.
Store the keys you've handled for a day or two and, when one comes again, answer as you did the first time without doing the work twice. All the example receivers in this guide do this.
Subcontext also keeps its own record. Once a send has succeeded under a key, it isn't sent again.
Too many failures in a row#
If a connection fails with a temporary problem 10 times in a row, Subcontext pauses it so it stops calling a system that is down, and tells your firm's owners and managers. While it's paused, the connection shows paused on its page, agent tools say the system is temporarily unavailable, and workflow and lifecycle sends are recorded as failed.
A paused connection comes back in two ways:
- Every 15 minutes, Subcontext lets one send through. If it succeeds, the connection carries on as normal. If it fails, the connection waits another 15 minutes. A custom integration with a pull endpoint uses its next pull as that check.
- Resume on the connection's page (or a custom integration's page) puts it back to work at once. So does saving the connection, or a Test on one of its endpoints that passes.
A successful call also resets the failure count. Workflow sends that failed while it was paused can be sent again with Retry on the workflow's page.
Credentials#
On a custom integration, credentials are stored as secret variables. They
are encrypted, hidden in the Integration logs, and replaced by [redacted]
wherever they would appear in a stored request or response. Rotate Secret changes one without
touching the rest of the setup.
How each kind of request authenticates:
| Request | How it proves it's from Subcontext |
|---|---|
| Agent tools and Send to an integration | The credential you choose on the custom integration: an API key header, a bearer token, basic auth, or OAuth2 client credentials. See Authentication. |
| Send a webhook | An HMAC signature in X-Subcontext-Signature. See Check the signature. |
Logs#
Every call (and every delivery your system sends in) is in Integrations, Integration logs: the time, the address, your status, how long it took, any error, and the first 2,000 characters of the request and response bodies, with secrets removed. "No response" means the request never got an answer, for example a timeout. See Troubleshooting.