Tendkit

Search documentation

Find a docs page

Get Started

Signed API

Tickets from your own forms and backends, with signed requests.

Install

Terminal
npx shadcn@latest add @tendkit/channel-api

It lands in lib/support/channels/. Import apiChannel from there.

Set up

  1. Generate a secret

    "whsec_" plus the base64 of 32 random bytes. Keep it on your server.

  2. Configure the channel

    Usually with kind: "email", so replies go out by email through your email channel.

  3. Post from your backend

    Sign each request with signApiRequest(secret, body) and POST the JSON message to the channel's webhook URL. Never sign in a browser.

Configure

lib/support/index.ts
const email = resendChannel({ /* … */ });

apiChannel({
  id: "contact-form",
  secret: process.env.CONTACT_FORM_SECRET!,
  address: "Contact form",
  kind: "email",
  replyAddress: "support@in.acme.com",
  deliver: (message) => email.send(message),
  capabilities: email.capabilities,
})

// In your contact form's server action:
const body = JSON.stringify({ id: submission.id, from: { email, name }, subject, text });
await fetch("https://acme.com/api/webhooks/support/contact-form", {
  method: "POST",
  headers: await signApiRequest(process.env.CONTACT_FORM_SECRET!, body),
  body,
});

Options

NameTypeDescription
id*stringStable channel id, stored on every ticket and used in the webhook URL. Never change it.
secret*string"whsec_" + base64 of 24 to 64 random bytes, shared with the app that signs.
address*stringWhere the ticket came from, such as "Contact form".
deliver*(message) => SendResultSends replies. Usually your email channel's send.
kind"api" | "email""email" gives replies a subject, HTML, acknowledgements and a threading reply address.Default: "api"
replyAddressstringThe email channel's receiving address, so answers return to the ticket.
capabilitiesPartial<ChannelCapabilities>Usually the delivering channel's.

Behaviour

  • Resending the same message id is a no-op, so retries are safe.
  • API messages are untrusted: they never link to an account, so a form can't impersonate a customer.