Calling your URL

The signed request your own code receives, and external tools.

Your own code runs on your host, and call_url is how a relay reaches it. Gemmein POSTs one JSON body:

{
  "id": "…",                                // the event id, the same on every retry and replay
  "relay": { "id": "…", "name": "gocardless-paid" },
  "trigger": "receiver",                     // receiver | schedule | data_change | credits_low | run_started
  "event": { … },                            // the stored payload: { event, mapped } | { record, previous } | { tick }
  "person": { "id": "…", "email": "…" },    // or null
  "results": [ … ]                           // the actions that ran before this one
}

The request carries content-type: application/json, user-agent: Gemmein-Relays/1 and X-Gemmein-Signature: t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256 of the timestamp, a dot, then the body, with the relay's signing secret. That secret is shown once when the relay is created and can be rotated. Verify over the raw body, in constant time, before trusting anything:

import { createHmac, timingSafeEqual } from "node:crypto"

const [t, v1] = req.headers["x-gemmein-signature"].split(",").map(p => p.slice(p.indexOf("=") + 1))
const expected = createHmac("sha256", process.env.GEMMEIN_SIGNING_SECRET).update(`${t}.${rawBody}`).digest("hex")
const ok = expected.length === v1.length && timingSafeEqual(Buffer.from(expected), Buffer.from(v1))

A 2xx answer is done. A redirect is a failure and is never followed. The call times out after ten seconds. The status and the first 4 KB of the answer are kept on the event, so you can read what your URL said.

Your URL must deduplicate on id. The call is not idempotent on Gemmein's side: a replay, a retry after the relay was edited, and a call cut off by a deploy POST the same id again. Keep the ids you have handled. The whole event is in the body, so the URL receives private data; it is your host, over https.

Refused addresses arrive as the action's error, naming the rule: https_only, ip_literal, own_host, unresolvable, private_address. Gemmein calls public addresses only, checks the address on every attempt, and pins the connection to the address it checked.

External tools

A tool whose provider is external does not use a relay: starting a run POSTs it straight to the tool's own URL, signed and retried, with no relay event. The request body, the /complete, /fail and /progress endpoints and the g.pipelines.handle helper are on Your own pipeline.

Was this page helpful?

Last updated