Integration manual

Secure webhook actions

Use IPIC to send a selected IP address or monitoring event to a system you control. This guide explains the easy standard setup, the stronger signed setup and the safe relay option for older GET-only systems.

Important: IPIC sends a request. Your receiving system decides what happens next. Always test with a harmless action before enabling blocking, deletion or other destructive work.

1. Choose the right security mode

Recommended for most admins

Standard secure POST

Uses HTTPS and a dedicated bearer token or API-key header. It is easier to configure and works with many APIs and automation platforms.

  • Lowest setup effort
  • Dedicated secret can be revoked
  • Good protection when HTTPS and a strong token are used
Go to the standard guide
Strongest verification

Advanced signed webhook

Uses a private signing secret, request timestamp and unique delivery ID. The receiver can prove that the body was not changed and reject replayed requests.

  • Protects message integrity
  • Supports replay protection
  • Requires receiver-side code
Go to the signed guide
Legacy compatibility

GET-only relay

IPIC still sends a secure POST. A small script on your own server then calls the older GET-only system. Its private credentials remain on your server.

  • No direct GET action in IPIC
  • Keeps legacy keys out of IPIC
  • Requires a small relay script
Go to the relay guide

2. Before you begin

  1. Create a dedicated receiver.
    Use a separate endpoint for IPIC. Do not reuse a normal login page or expose a general administrator API.
  2. Use HTTPS.
    The certificate must be valid and the final URL must be publicly reachable on port 443.
  3. Create a dedicated secret.
    Do not use your firewall password, hosting-panel password or main API credential.
  4. Decide which actions are allowed.
    Your receiver should allow only known actions such as block_ip or create_ticket.
  5. Make test mode harmless.
    When the payload contains "test": true, acknowledge the request but do not perform a live action.

Easier setup

3. Standard secure POST

What you enter in IPIC

Action nameBlock IP in firewall
Endpointhttps://security.example.com/ipic/block
AuthenticationBearer token
ConfirmationRequired

Request sent by IPIC

POST /ipic/block HTTP/1.1
Host: security.example.com
Authorization: Bearer YOUR_DEDICATED_TOKEN
Content-Type: application/json

{
  "version": "1.0",
  "event": "ip.action.requested",
  "action": "block_ip",
  "ip": "203.0.113.24",
  "request_id": "3906b69b-79ba-49e9-b092-c056b9ad8f83",
  "requested_at": "2026-07-23T18:00:00Z",
  "test": false
}
Complete PHP receiver example
<?php
declare(strict_types=1);

const IPIC_TOKEN = 'replace-with-a-long-random-token';
header('Content-Type: application/json');

if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
    http_response_code(405);
    echo json_encode(['success' => false, 'error' => 'POST requests only']);
    exit;
}

$received = trim((string)($_SERVER['HTTP_AUTHORIZATION'] ?? ''));
if (!hash_equals('Bearer ' . IPIC_TOKEN, $received)) {
    http_response_code(401);
    echo json_encode(['success' => false, 'error' => 'Invalid token']);
    exit;
}

$body = (string)file_get_contents('php://input');
$data = json_decode($body, true);
if (!is_array($data)) {
    http_response_code(400);
    echo json_encode(['success' => false, 'error' => 'Invalid JSON']);
    exit;
}

$ip = trim((string)($data['ip'] ?? ''));
$action = trim((string)($data['action'] ?? ''));
if (filter_var($ip, FILTER_VALIDATE_IP) === false) {
    http_response_code(422);
    echo json_encode(['success' => false, 'error' => 'Invalid IP address']);
    exit;
}
if (!in_array($action, ['block_ip', 'unblock_ip', 'create_ticket'], true)) {
    http_response_code(422);
    echo json_encode(['success' => false, 'error' => 'Unsupported action']);
    exit;
}
if (!empty($data['test'])) {
    echo json_encode(['success' => true, 'message' => 'Test accepted. No live action performed.']);
    exit;
}

// Call your own firewall, ticketing or automation API here.
// Never place $ip directly into an unescaped shell command.

echo json_encode([
    'success' => true,
    'message' => 'Request accepted',
    'request_id' => (string)($data['request_id'] ?? '')
]);
Use this mode when: your system already accepts bearer tokens or API-key headers and you want the simplest safe configuration.

Stronger verification

4. Advanced signed webhook

IPIC calculates an HMAC-SHA256 signature from the exact timestamp and raw request body. The receiver repeats the calculation using the same private secret.

Headers sent by IPIC

X-IPIC-Timestamp: 1784829600
X-IPIC-Delivery: 3906b69b-79ba-49e9-b092-c056b9ad8f83
X-IPIC-Signature: sha256=4b3a12db7cfd32b68c3c52f3213bb64...

Checks the receiver must perform

  1. Require POST and a valid JSON content type.
  2. Read the raw body before decoding or reformatting it.
  3. Reject timestamps older than five minutes.
  4. Calculate the expected signature from timestamp.raw_body.
  5. Compare signatures using a timing-safe comparison.
  6. Reject a delivery ID that has already been processed.
  7. Validate the action and IP address.
  8. Do nothing live when test is true.
Complete PHP signed receiver example
<?php
declare(strict_types=1);

const IPIC_SIGNING_SECRET = 'copy-the-generated-secret-here';
const REPLAY_STORE = __DIR__ . '/ipic-deliveries.json';
header('Content-Type: application/json');

$body = (string)file_get_contents('php://input');
$timestamp = trim((string)($_SERVER['HTTP_X_IPIC_TIMESTAMP'] ?? ''));
$delivery = trim((string)($_SERVER['HTTP_X_IPIC_DELIVERY'] ?? ''));
$received = trim((string)($_SERVER['HTTP_X_IPIC_SIGNATURE'] ?? ''));

if (!ctype_digit($timestamp) || abs(time() - (int)$timestamp) > 300) {
    http_response_code(401); echo json_encode(['success'=>false,'error'=>'Request expired']); exit;
}
if ($delivery === '' || !preg_match('/^[a-f0-9-]{20,80}$/i', $delivery)) {
    http_response_code(401); echo json_encode(['success'=>false,'error'=>'Invalid delivery ID']); exit;
}
$expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $body, IPIC_SIGNING_SECRET);
if (!hash_equals($expected, $received)) {
    http_response_code(401); echo json_encode(['success'=>false,'error'=>'Invalid signature']); exit;
}

$seen = file_exists(REPLAY_STORE) ? json_decode((string)file_get_contents(REPLAY_STORE), true) : [];
$seen = is_array($seen) ? $seen : [];
$seen = array_filter($seen, fn($time) => (int)$time > time() - 86400);
if (isset($seen[$delivery])) {
    http_response_code(409); echo json_encode(['success'=>false,'error'=>'Duplicate delivery']); exit;
}
$seen[$delivery] = time();
file_put_contents(REPLAY_STORE, json_encode($seen), LOCK_EX);

$data = json_decode($body, true);
$ip = trim((string)($data['ip'] ?? ''));
$action = trim((string)($data['action'] ?? ''));
if (!is_array($data) || filter_var($ip, FILTER_VALIDATE_IP) === false) {
    http_response_code(422); echo json_encode(['success'=>false,'error'=>'Invalid payload']); exit;
}
if (!in_array($action, ['block_ip','unblock_ip','create_ticket'], true)) {
    http_response_code(422); echo json_encode(['success'=>false,'error'=>'Unsupported action']); exit;
}
if (!empty($data['test'])) {
    echo json_encode(['success'=>true,'message'=>'Signed test accepted']); exit;
}

// Perform the local action here.
echo json_encode(['success'=>true,'message'=>'Signed request accepted','delivery'=>$delivery]);

The small JSON replay store above is suitable only as a simple example. Busy or multi-server systems should store delivery IDs in a database or shared cache with a unique constraint.

Older systems

5. Safe relay for a GET-only destination

Do not put the old GET URL into IPIC. Install a relay on your server. The relay accepts IPIC’s secure POST, validates it, then makes the older request using credentials stored only on your server.

IPIC secure POST → your relay → old GET-only endpoint
PHP relay example
<?php
declare(strict_types=1);
const IPIC_TOKEN = 'replace-with-ipic-token';
const LEGACY_URL = 'https://legacy.example.com/block.php';
const LEGACY_KEY = 'keep-this-only-on-your-server';

$auth = trim((string)($_SERVER['HTTP_AUTHORIZATION'] ?? ''));
if (!hash_equals('Bearer ' . IPIC_TOKEN, $auth)) { http_response_code(401); exit('Unauthorised'); }
$data = json_decode((string)file_get_contents('php://input'), true);
$ip = trim((string)($data['ip'] ?? ''));
if (filter_var($ip, FILTER_VALIDATE_IP) === false) { http_response_code(422); exit('Invalid IP'); }
if (!empty($data['test'])) { echo json_encode(['success'=>true,'message'=>'Relay test accepted']); exit; }

$url = LEGACY_URL . '?' . http_build_query(['ip' => $ip, 'key' => LEGACY_KEY]);
$ch = curl_init($url);
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_FOLLOWLOCATION=>false, CURLOPT_TIMEOUT=>10, CURLOPT_SSL_VERIFYPEER=>true, CURLOPT_SSL_VERIFYHOST=>2]);
$response = curl_exec($ch); $status = (int)curl_getinfo($ch, CURLINFO_RESPONSE_CODE); $error = curl_error($ch); curl_close($ch);
if ($response === false || $status < 200 || $status >= 300) { http_response_code(502); echo json_encode(['success'=>false,'error'=>'Legacy request failed','remote_status'=>$status]); exit; }
echo json_encode(['success'=>true,'message'=>'Legacy system accepted the request']);

6. Safe testing procedure

  1. Create the action but leave it disabled.
  2. Install and configure the receiver. Copy the dedicated token or signing secret once.
  3. Send a test request. IPIC uses a reserved example IP and sets test to true.
  4. Confirm no live change occurred. The receiver should return HTTP 200 and a harmless test message.
  5. Enable the action. Keep confirmation required for destructive actions.
  6. Run one controlled live test. Use a safe test environment or an address you can immediately reverse.

7. Responses IPIC understands

Successful response

HTTP/1.1 200 OK
Content-Type: application/json

{"success":true,"message":"IP accepted"}

Rejected response

HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json

{"success":false,"error":"Invalid IP address"}

Return a clear HTTP status quickly. Queue slow work after accepting the request rather than making IPIC wait for a long firewall or ticketing operation.

8. Troubleshooting

MessageLikely reasonWhat to do
Private destination blockedThe hostname resolves to a local, loopback, link-local or reserved address.Use a publicly reachable HTTPS endpoint. IPIC intentionally cannot call internal network addresses.
Certificate validation failedThe certificate is expired, self-signed or does not match the hostname.Install a valid public certificate and use the exact certified hostname.
HTTP 401 or 403The token, API-key header or signature is wrong.Copy the secret again, check the header name and ensure no spaces or quotes were added.
Signature invalidThe receiver signed reformatted JSON rather than the exact raw body.Calculate the signature before decoding or changing the body.
Request expiredThe server clock differs or processing took too long.Synchronise the receiver’s clock and allow a maximum age of five minutes.
Duplicate deliveryThe same delivery ID was already processed.Return a safe duplicate response and do not repeat the live action.
TimeoutThe receiver is slow, unreachable or waiting on another service.Respond quickly and queue long-running work.
Redirect rejectedThe entered URL redirects to another page.Enter the final HTTPS endpoint directly. IPIC does not follow redirects.
HTTP 500The receiver crashed or its local integration failed.Check the receiver’s own server log. Do not send secrets to support.

9. Rotate, disable or remove access

When a secret may be exposed

  1. Disable the IPIC action immediately.
  2. Generate a new token or rotate the signing secret.
  3. Update the receiver with the new value.
  4. Send a test request.
  5. Re-enable the action only after the test succeeds.
  6. Delete the old secret from the receiver and any password manager entry that is no longer needed.

When removing an integration

Disable or delete the action in IPIC, revoke the token on the receiving system and remove the receiver script if it is no longer used. Existing delivery logs may remain for audit and troubleshooting, but secrets must never be stored in those logs.

10. Final security checklist

Return to the top