Example file

agent-tool-endpoint.php

PHP: the same agent tool endpoint, for PHP's built-in web server or your own.

agent-tool-endpoint.php · PHP Download
<?php
// An endpoint for a Subcontext agent tool, in PHP 8.1 or later with no packages.
//
// It offers two operations you can add to a custom integration on Subcontext:
//
//   GET  /policies/{{ reference }}   look up a policy (the agent sends date_of_birth too)
//   POST /callbacks                  book a callback (the body comes from your body template)
//
// Subcontext sends your API key in the X-API-Key header (the integration's
// Auth type "Api key"). Writes carry an Idempotency-Key header that stays the
// same if the same tool call is sent again, so this file answers a repeat with
// the first answer instead of booking twice.
//
// Try it with PHP's built-in web server:
//   SUBCONTEXT_TOOL_KEY=A_LONG_RANDOM_VALUE php -S localhost:4100 agent-tool-endpoint.php
// Subcontext only calls https addresses on the public internet, so in real use
// serve it from your usual HTTPS web server.

declare(strict_types=1);

// The value you store as a secret variable on the integration in Subcontext.
$apiKey = getenv('SUBCONTEXT_TOOL_KEY') ?: '';

// Stand-ins for your own systems.
$policies = [
    'POL-1001' => ['date_of_birth' => '1980-04-12', 'status' => 'active',
                   'renewal_date' => '2027-03-01', 'monthly_premium' => '18.40'],
];
// Answers already given to writes, by Idempotency-Key. Use your database in real code.
$answeredFile = sys_get_temp_dir() . '/subcontext-tool-answers.json';

function sendJson(int $status, array $body): void
{
    http_response_code($status);
    header('Content-Type: application/json');
    echo json_encode($body);
    exit;
}

if ($apiKey === '') {
    sendJson(500, ['error' => 'Set SUBCONTEXT_TOOL_KEY to the API key Subcontext sends.']);
}

// 1. Check the key. A 401 tells Subcontext the credentials are wrong.
$given = $_SERVER['HTTP_X_API_KEY'] ?? '';
if (!hash_equals($apiKey, $given)) {
    sendJson(401, ['error' => 'Unknown API key.']);
}

$method = $_SERVER['REQUEST_METHOD'];
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) ?: '/';

// 2. A read: GET /policies/POL-1001?date_of_birth=1980-04-12
if ($method === 'GET' && preg_match('#^/policies/([^/]+)$#', $path, $match)) {
    $reference = strtoupper(rawurldecode($match[1]));
    $policy = $policies[$reference] ?? null;
    // Ask for something only the customer knows before giving anything away.
    if ($policy === null || $policy['date_of_birth'] !== ($_GET['date_of_birth'] ?? '')) {
        // The agent sees this status and message, so make the message useful.
        sendJson(404, ['error' => 'No policy matches that reference and date of birth. Ask the customer to check both.']);
    }
    // Small, flat and only what the agent needs to answer.
    sendJson(200, [
        'reference' => $reference,
        'status' => $policy['status'],
        'renewal_date' => $policy['renewal_date'],
        'monthly_premium' => $policy['monthly_premium'],
    ]);
}

// 3. A write: POST /callbacks with the body your template builds.
if ($method === 'POST' && $path === '/callbacks') {
    $answered = is_file($answeredFile) ? (json_decode((string) file_get_contents($answeredFile), true) ?: []) : [];
    $idempotencyKey = $_SERVER['HTTP_IDEMPOTENCY_KEY'] ?? '';
    if ($idempotencyKey !== '' && isset($answered[$idempotencyKey])) {
        sendJson($answered[$idempotencyKey]['status'], $answered[$idempotencyKey]['body']);
    }

    $body = json_decode((string) file_get_contents('php://input'), true);
    if (!is_array($body)) {
        sendJson(400, ['error' => "The request body wasn't valid JSON."]);
    }
    if (empty($body['phone'])) {
        sendJson(422, ['error' => 'A phone number is needed to book a callback. Ask the customer for one.']);
    }

    $answer = [
        'status' => 201,
        'body' => [
            'callback_id' => bin2hex(random_bytes(16)),
            'message' => 'Callback booked for ' . ($body['preferred_time'] ?? 'the next available slot') . '.',
        ],
    ];
    if ($idempotencyKey !== '') {
        $answered[$idempotencyKey] = $answer;
        file_put_contents($answeredFile, json_encode($answered), LOCK_EX);
    }
    sendJson($answer['status'], $answer['body']);
}

sendJson(404, ['error' => 'No such operation.']);

Stuck, or something missing?

Get in touch and a person on the team will help.

Contact us