Writing a connector
Turn what another system sends into deliveries, and push them over a connection.
What a connector is
A connector sits between a system you use, such as a billing system, and Norra. The other system sends the connector its events. The connector turns them into deliveries and pushes them to Norra over a service connection that may write.
Norra does not call a connector, and does not fetch data from anywhere. Everything arrives by push, so the connector runs on your side, where the other system can reach it.
There is no SDK. A connector is any program that can make the request in this guide.
1. Describe what it sends
Write down, for each kind of row the connector produces:
- the fields, each a
number,text,timestamporbool; - the field that holds when the row happened, a
timestamp, sent as a date, a time and an offset (RFC 3339), such as2026-09-01T09:12:00Z, or as a date alone, such as2026-09-01, which is read as midnight on that day in the company's timezone; - every field that can name a person, such as an email or a customer name.
Each kind of row becomes one source in Norra. Add the source on the company's Sources page with those fields, and mark the personal ones. A personal field can be filtered on, but no metric can be grouped by it.
Then choose what each delivery is. This decides how you correct a number you already sent.
- Events. Each delivery adds its rows to what came before, like invoices or sign-ups. To correct one, send a row that cancels it, such as a negative amount or a refund. Most connectors that forward what another system tells them send events.
- Statements. Each delivery is the whole of every period it has a row in, like a daily or monthly report. The delivery received last for a period replaces what earlier ones said about it, and the period is marked restated on the metric's page, which names the delivery. To correct a number, send the whole period again.
A period is the metric's: a day, a week or a month. A statement with one day of September in it is the September statement for a monthly metric, so September then reads that one day. Send whole periods, or choose events.
Send a statements source its statement once a week, and on demand in between when a number has to change sooner. Each statement holds every day of each period it touches, from the period's first day to the day it is sent. A monthly metric then reads the month so far, not one day of it.
2. Make a connection that may write
Make a connection for each source on the Developer page, and choose Write. Keep Client id and secret as how it signs in: the connector asks the token endpoint for a token and pushes with the token, so the secret stays with the connector. Use a static key only if the connector cannot ask for a token. The connections guide says how both work.
Hold one token at a time. Ask for it before the first push, use it until about a minute before it expires, then ask for a new one.
3. Turn each event into a delivery
A delivery is one push. Its idempotency key has to come from what the other system sent, such as its event id or batch id. Do not use a clock or a random value. The other system resends when it misses an answer, and the same input has to give the same key so Norra stores it once.
Send one delivery per source, with the rows in payload:
curl https://app.norra-co.com/v1/ingest \
--request POST \
--header "Authorization: Bearer $NORRA_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"idempotency_key": "billing:b_2026_09_01:invoices",
"shape_version": 1,
"payload": [
{
"paid_at": "2026-09-01T09:12:00Z",
"amount": "1250.00",
"plan": "pro",
"email": "anna@example.com"
}
]
}'shape_version is the source's version in Norra, which is the one on the Sources page. Values are strings, so an amount stays exact.
4. Act on the answer
202: stored. Keep theartifact_id, and do not send it again.deliverssays whether the source took it as events or as a statement.401on a token you have used before: ask for a new token once and send the same delivery again. The token may have been revoked while the connection still works.- A second
401, a401on a new token, a refused token request, or a403: the credential is refused. Stop, and record it ascredential_refused. Sending again gets the same answer, so a person has to fix the connection. - Any other
4xx: Norra will not take this delivery as it is. Stop, and record it asdelivery_refused. 429: the rate limit. Wait at least the seconds inRetry-After, then send the same delivery again with the same idempotency key. Record it asrate_limited.- A
5xx, a timeout or no answer: wait, starting at 30 seconds and doubling, then send the same delivery again. Record it asunavailable.
The errors guide has every status.
Built into Norra
A connector Norra runs itself is a Go package under internal/metrics/connector. It declares a manifest (its name and version, what it sends, the kind of credential it is written for, its personal fields), a receive function, a health check and sample input. It pushes through the contract's session, which holds the token.
It is admitted when it passes the conformance suite, which checks ten rules:
- the manifest is well formed and the connector is healthy;
- every row it produces fits its shape;
- the same input sent twice is stored once;
- a refused credential stops it;
- the rate limit makes it wait at least
Retry-After, then retry; - no metric can be grouped by a field it marks personal;
- a token is reused, and renewed a minute before it expires;
- a
401gets one new token and one more try, and the second401is the answer; - a revoked connection is refused and stores nothing;
- the client secret stays off
/v1/ingest, and no secret or token reaches a log.
The suite runs rules 3, 4, 5, 9 and 10 twice, once with client credentials and once with a static key, because a company may make either for the connector's source.
The same rules apply to a connector you write yourself.