Developer Tools

Complete integration examples

Copy complete backend examples for Node/Express, Next.js, Laravel, Python, and plain PHP.

Node and Express

Expose your own authenticated backend route. The browser calls your server; your server calls HookMessage with the API key.

express-send.ts
ts
import express from "express";

const app = express();
app.use(express.json());

app.post("/orders/:orderId/send-confirmation", async (req, res, next) => {
  try {
    const response = await fetch(`${process.env.HOOKMESSAGE_BASE_URL}/api/messages/send`, {
      method: "POST",
      headers: {
        "content-type": "application/json",
        "x-api-key": process.env.HOOKMESSAGE_API_KEY!,
      },
      body: JSON.stringify({
        sender_id: process.env.HOOKMESSAGE_SENDER_ID,
        receiver_number: req.body.phone,
        message: `Order ${req.params.orderId} is confirmed.`,
        source_system: "express-api",
        source_reference: req.params.orderId,
        idempotency_key: `order:${req.params.orderId}:confirmed`,
      }),
    });

    const data = await response.json();
    if (!response.ok) return res.status(response.status).json(data);
    return res.status(201).json({ messageId: data.message_id, status: data.status });
  } catch (error) {
    next(error);
  }
});

Next.js route handler

Keep the API key in server environment variables and return only your own safe response to the client.

app/api/send-confirmation/route.ts
ts
export async function POST(request: Request) {
  const body = await request.json();

  const response = await fetch(`${process.env.HOOKMESSAGE_BASE_URL}/api/messages/send`, {
    method: "POST",
    headers: {
      "content-type": "application/json",
      "x-api-key": process.env.HOOKMESSAGE_API_KEY!,
    },
    body: JSON.stringify({
      sender_id: process.env.HOOKMESSAGE_SENDER_ID,
      receiver_number: body.phone,
      message: "Your request was received.",
      source_system: "nextjs",
      source_reference: body.requestId,
      idempotency_key: `request:${body.requestId}:received`,
    }),
  });

  const data = await response.json();
  if (!response.ok) return Response.json(data, { status: response.status });
  return Response.json({ messageId: data.message_id, status: data.status });
}

Laravel service and controller

Wrap HookMessage behind a service so controllers do not duplicate headers, idempotency, or error handling.

Laravel
php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Route;

final class HookMessageService
{
    public function sendConfirmation(string $phone, string $orderId): array
    {
        $response = Http::baseUrl(config('services.hookmessage.url'))
            ->withHeaders(['x-api-key' => config('services.hookmessage.key')])
            ->post('/api/messages/send', [
                'sender_id' => config('services.hookmessage.sender_id'),
                'receiver_number' => $phone,
                'message' => "Order {$orderId} is confirmed.",
                'source_system' => 'laravel',
                'source_reference' => $orderId,
                'idempotency_key' => "order:{$orderId}:confirmed",
            ]);

        $response->throw();
        return $response->json();
    }
}

Route::post('/orders/{order}/send-confirmation', function (Request $request, string $order, HookMessageService $hookMessage) {
    return response()->json($hookMessage->sendConfirmation($request->string('phone'), $order));
});

Python Flask and FastAPI

The same server-side pattern works in Flask or FastAPI. Keep the API key out of browser code.

python-web.py
py
import os
import requests
from flask import Flask, request, jsonify
from fastapi import FastAPI

flask_app = Flask(__name__)
fastapi_app = FastAPI()

def send_confirmation(phone: str, reference: str) -> dict:
    response = requests.post(
        f"{os.environ['HOOKMESSAGE_BASE_URL']}/api/messages/send",
        headers={"x-api-key": os.environ["HOOKMESSAGE_API_KEY"]},
        json={
            "sender_id": os.environ["HOOKMESSAGE_SENDER_ID"],
            "receiver_number": phone,
            "message": "Your request was received.",
            "source_system": "python",
            "source_reference": reference,
            "idempotency_key": f"request:{reference}:received",
        },
        timeout=20,
    )
    response.raise_for_status()
    return response.json()

@flask_app.post("/send-confirmation")
def flask_send_confirmation():
    payload = request.get_json()
    return jsonify(send_confirmation(payload["phone"], payload["request_id"]))

@fastapi_app.post("/send-confirmation")
def fastapi_send_confirmation(payload: dict):
    return send_confirmation(payload["phone"], payload["request_id"])

Plain PHP endpoint

Plain PHP should still keep secrets server-side and return only safe response fields.

send-confirmation.php
php
<?php

$input = json_decode(file_get_contents('php://input'), true);
$payload = json_encode([
    'sender_id' => getenv('HOOKMESSAGE_SENDER_ID'),
    'receiver_number' => $input['phone'],
    'message' => 'Your request was received.',
    'source_system' => 'plain-php',
    'source_reference' => $input['request_id'],
    'idempotency_key' => 'request:' . $input['request_id'] . ':received',
], JSON_THROW_ON_ERROR);

$ch = curl_init(rtrim(getenv('HOOKMESSAGE_BASE_URL'), '/') . '/api/messages/send');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'content-type: application/json',
        'x-api-key: ' . getenv('HOOKMESSAGE_API_KEY'),
    ],
    CURLOPT_POSTFIELDS => $payload,
    CURLOPT_TIMEOUT => 20,
]);

$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

http_response_code($status);
header('content-type: application/json');
echo $body;

Need help?

Use Book Integration Help if you want setup help for OTP or automated customer messaging.

Next steps