CoDNA Request access
Contents

Docs Decisions

0027 · Another app starts an automation by sending to an address CoDNA gives it

Status: Accepted · 2026-11-01. It adds a kind to the connections of 0025 and a third way for an automation to start, beside events and a schedule (automations.md). The sandbox (0007) does not change.

Context

An automation starts when something happens in CoDNA, or on a schedule. Nothing outside can start one. With connections, sources and a model (0023, 0025, 0026), CoDNA can read another system, write a page and tell people. But it can only do that on a timer, never when the other system has something to say:

  • a form is filled in, and the answer should be filed and the team told;
  • a payment arrives, a deal closes or a ticket is opened, and it belongs in the company's memory;
  • GitHub publishes a release, and the changelog page should get a line;
  • an n8n or Zapier flow has finished its plumbing, and its result should become a page.

These apps all tell other systems the same way: they POST JSON to an address they were given, and retry when they get no 2xx. Most can't sign in with OAuth or hold a token in a header. The address is the credential, sometimes with a signature made with a shared secret.

Today the only way in is the API with an agent account's OAuth token (0021), which none of these apps can do. Without a way in, a team keeps n8n or Zapier for this, and the result never reaches CoDNA's memory.

What CoDNA already has fits the need:

  • A connection has an owner, a sealed secret, sharing for use, management's switch and view, and an audit record of every change (0025).
  • A run acts as its owner, with every action vetted. Its record says what started it, what it read and what it did.
  • Data from outside is handed to a script, never written anywhere unless the script writes it (0025's sources).

Decision

An incoming connection has an address

A new kind of connection, incoming, receives instead of sending.

  • Made on the Connections page by anyone the switch allows (0025), and by management for the instance. CoDNA makes its address, such as https://acme.codna.me/in/k3J9… with 43 random characters. Whoever has the address can send to it, so the address is the secret. It is kept sealed, and found by its hash.
  • Only its owner sees the address, on its page, to copy into the other app. It is never shown over MCP, to those it is shared with, or to management. Then an address isn't left in an AI conversation, as a secret isn't (0025).
  • Make a new address stops the old one at once. That is what to do when one has leaked.
  • Shared like any connection. Those it is shared with may have their automations listen to it. They never see its address.
  • Switched off or removed, it answers nothing but 404.

An automation listens to one

A third kind of automation, beside event and schedule: incoming, which names the connection it listens to.

create_automation  name: file-form-answers  from: signup-form

from is looked up the way a post's connection is (0025): the owner's own, else one shared with them, else the instance's. It is resolved when the automation is registered, and kept by the connection's id. Several automations may listen to one connection, and each runs on every request. The owner's use of it is checked on every run, as for a post.

The script is given the request:

export default function ({ event, actions }) {
  // event: { type: 'request.received', at, id, request }
  const { from, method, contentType, query, headers, body, text } = event.request
  // body: the JSON, or the form's fields; text: anything else, as it came
  actions.write({
    into: 'output:signups/' + event.at.slice(0, 10) + '-' + event.id,
    title: 'Sign-up: ' + body.company,
    content: '…',
  })
}

Headers come lowercased, without authorization, cookie or the signature's header. An inputs export works as it does on any run, so a script can read a source about what came in.

What the sender is told

  • 202 once the request is kept and its runs are queued. The runs come after, so the sender never waits on a script or learns what it did.
  • 200, and no run, when a request with the same delivery id came while that one is kept (7 days; proposed as 24 hours, and made the time a request is kept when it was built). The delivery id is the webhook-id, x-github-delivery or idempotency-key header, or the event's id on a connection that checks Stripe's signature. Apps retry, and a retried request should not file a second page.
  • 404 for an address that doesn't exist or was replaced, or a connection switched off. All three look the same, so an address can't be probed.
  • 401 when the connection checks signatures and this one doesn't match (below).
  • 405 for anything but POST, 413 above 1 MB, and 429, with Retry-After, above 60 requests a minute for one connection.

Checking who sent it

A connection may also check a signature, with a secret pasted on its page, as any secret is. It names one of the schemes apps use:

  • github: X-Hub-Signature-256;
  • stripe: Stripe-Signature;
  • standard-webhooks: the Standard Webhooks headers that Resend, Clerk, Svix and others send;
  • hmac-sha256: a header the owner names, hex or base64, for the rest (Shopify, Typeform…).

Then a request whose signature doesn't match is refused. For schemes with a time in them, so is one more than five minutes old. Without a check, the address alone is the credential, which is how n8n's, Zapier's and Slack's own addresses work.

What is kept, and how a script is written for it

  • Each request is kept for 7 days, with what it started. The owner sees the latest on the connection's page: when, from which address, how it was answered, and which runs it started.
  • A request that no automation listens to is still kept. The way to build one is to make the connection, put its address in the other app, and send that app's test event. Then the owner's agent reads what really arrived and writes the script for it.
  • list_connections gives the latest request on an incoming connection the person owns, its body cut at 16 KB. That is for writing the script, and only the owner's agent gets it.
  • run_automation runs an incoming automation on the latest request, or on one the caller gives. That works with a dry run and a preview too (0026).

Management

  • The switch on who makes connections (0025) covers incoming ones. Allowed hosts don't apply, since nothing is sent.
  • Management sees each one, with its owner, who it is shared with, and how many requests it took and refused. It can switch one off or remove it. It never sees an address or a secret.
  • The audit records each one made, given a new address, given a check, shared, switched off or removed.

Consequences

  • An instance has public addresses that start runs. Whoever has one can start the automations listening to it, as often as the limit allows, and give them any data. The run still does only what its script says, as its owner. A leaked address can make a script post noise or spend the model budget. The limit, the budget, signature checks and making a new address bound that.
  • What a sender wrote can reach the model, when a script asks one about a request, and it may carry instructions. The answer only ever becomes a page (0026), and the page says which automation wrote it. A sender can make a page say something untrue. They can't make an automation do anything its script doesn't.
  • What senders send is kept for 7 days, and may hold personal data: a form's answers, a buyer's email. Maintenance deletes it, and a run's record keeps the request only as its event, cut at 64 KB, for 30 days, as it keeps a source's answer.
  • Every instance answers POSTs on /in/. A request to an address that doesn't exist costs one lookup.
  • n8n stays useful for flows of many steps: this is the way in, not a workflow engine. An n8n flow can now end by sending to CoDNA, and CoDNA can already post to n8n.

Alternatives rejected

  • The API, with an agent account's token (today): the apps that send these can't sign in with OAuth, and most can't set a header.
  • An address per automation, with no connection: a signing secret would live on the automation, which an agent writes. Two automations couldn't listen to one form, and nothing would give management a view or keep sharing and leaving consistent. A connection already does all of that.
  • The script answers the sender, as n8n's "Respond to Webhook" does: the sender would wait on the run and its reads, up to 30 seconds. An answer built from what the owner can read also lets whoever holds the address read it.
  • Each request becomes a page that fires document.created: a page of JSON for every request, in search and in people's inboxes. The script decides what is worth a page.
  • GET, any method, and large bodies: these apps POST JSON or a form. A GET handshake, such as Meta's, or a file upload is a different thing (see Still open).

Settled when it was accepted

Tornike accepted it as proposed on 2026-11-01, settling:

  1. The address is shown to its owner whenever they open the connection.
  2. The limits: 1 MB a request, 60 a minute for each connection, requests kept for 7 days.
  3. Signature checks come in the second step. Until then, the address is the credential.

Still open

  • Handshakes that need an answer: Slack's url_verification (and with it Slack's signature), Meta's GET challenge, Microsoft Graph's validation token.
  • Keeping a request as a raw page, as evidence, the way an ingestion is. A script writes only in output today (0026).
  • Signing in with OAuth, for apps that register their webhooks through an API.

Building it

  1. Incoming connections: the kind, the address and /in/, keeping requests, the event, the runs, the answers, delivery ids and the limits. Then the Connections page, from in create_automation and the automation form, the latest request in list_connections, a request in run_automation, management's view and the audit.
  2. Signature checks: the four schemes, with the secret pasted on the connection's page. A refused request is kept with why, so its owner sees what was wrong.
  3. Docs: automations.md, the set-up-an-automation skill, and the guide to receiving from another app.