Receiving messages▾

Receiving messages

Stand up a webhook receiver end to end: verify, acknowledge, enqueue, reply.

A complete inbound path in four moves. The two that people get wrong are verifying against the raw body and replying before the work is done.

  1. 1

    Answer the verification challenge

    Registering an endpoint triggers a one-off GET ?challenge=…. Echo the token as plain text.

    Node
    app.get("/hooks/sendport", (req, res) =>
      res.type("text/plain").send(String(req.query.challenge ?? "")),
    );
  2. 2

    Verify the signature

    Use the raw body — see verifying signatures. Never parse before verifying.

  3. 3

    Acknowledge, then work

    Return 200 inside 5 seconds and push the event onto a queue. Doing the work inline is how you end up with duplicate deliveries.

    Node
    app.post("/hooks/sendport", express.raw({ type: "application/json" }), (req, res) => {
      if (!verify(req.body, req.headers["sendport-signature"] as string, SECRET)) {
        return res.status(400).send("bad signature");
      }
    
      const event = JSON.parse(req.body.toString());
      res.sendStatus(200);
    
      if (event.type === "message.received") queue.push(event);
    });
  4. 4

    Reply inside the window

    An inbound message opens 24 hours of free-form replies. Read data.session_expires_at on the event and send text while it is in the future; after that you need a template.

    Node
    await fetch("https://api.sendport.tech/v1/messages", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.SENDPORT_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        to: event.data.from,
        type: "text",
        text: { body: "Thanks — checking now." },
      }),
    });

Testing locally

Webhooks cannot reach localhost. Use sendport webhooks listen to tunnel deliveries to your machine — see the CLI.

Shell
sendport webhooks listen --forward http://localhost:3000/hooks/sendport

Deduplicate on the envelope id

Retries reuse evt_…. Store seen ids for 24 hours and drop repeats — every handler must be idempotent.