Integration guide
HurrySign is a website and an API. Keep API keys on your server. The browser only ever sees a short-lived signing URL.
Machine-readable spec: /api/v1/openapi.json
Partner accounts and sub-organizations
A firm that only sends its own agreements uses one organization. A product such as a claims CRM or a mitigation platform turns on “partner” in settings, then calls POST /api/v1/sub-organizations. Each customer can be a child organization billed to the partner (billingMode: parent) or billed on its own. Act as a child by sending X-HurrySign-Organization: <child id> with the partner key, or store the API key returned when the child is created. X-SignPact-Organization is still accepted.
Create and send — Node
const base = "https://your-host.example";
const headers = { Authorization: "Bearer " + process.env.HURRYSIGN_API_KEY, "Content-Type": "application/json" };
const created = await fetch(base + "/api/v1/envelopes", {
method: "POST",
headers,
body: JSON.stringify({
subject: "Service agreement",
signingOrder: "sequential",
recipients: [{ name: "Ada Lovelace", email: "ada@example.com", role: "signer", routingOrder: 1 }],
}),
}).then((res) => res.json());
const form = new FormData();
form.append("file", new Blob([fs.readFileSync("agreement.pdf")], { type: "application/pdf" }), "agreement.pdf");
const uploaded = await fetch(base + "/api/v1/envelopes/" + created.envelope.id + "/documents", {
method: "POST",
headers: { Authorization: headers.Authorization },
body: form,
}).then((res) => res.json());
await fetch(base + "/api/v1/envelopes/" + created.envelope.id + "/fields", {
method: "POST",
headers,
body: JSON.stringify({
documentId: uploaded.document.id,
recipientId: created.envelope.recipients[0].id,
type: "signature",
anchor: "{{sig_1}}",
}),
});
await fetch(base + "/api/v1/envelopes/" + created.envelope.id + "/send", {
method: "POST",
headers,
body: JSON.stringify({ confirmExclusions: true }),
});Create and send — PHP
<?php
$base = "https://your-host.example";
$key = getenv("HURRYSIGN_API_KEY");
function hurrysign_json($method, $url, $key, $body = null) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer $key", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body ? json_encode($body) : null,
]);
$raw = curl_exec($ch);
curl_close($ch);
return json_decode($raw, true);
}
$created = hurrysign_json("POST", "$base/api/v1/envelopes", $key, [
"subject" => "Service agreement",
"recipients" => [["name" => "Ada Lovelace", "email" => "ada@example.com", "role" => "signer"]],
]);
$id = $created["envelope"]["id"];
$ch = curl_init("$base/api/v1/envelopes/$id/documents");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer $key"],
CURLOPT_POSTFIELDS => ["file" => new CURLFile("agreement.pdf", "application/pdf", "agreement.pdf")],
]);
$uploaded = json_decode(curl_exec($ch), true);
curl_close($ch);
hurrysign_json("POST", "$base/api/v1/envelopes/$id/fields", $key, [
"documentId" => $uploaded["document"]["id"],
"recipientId" => $created["envelope"]["recipients"][0]["id"],
"type" => "signature",
"page" => 1, "x" => 0.12, "y" => 0.72, "width" => 0.36, "height" => 0.07,
]);
hurrysign_json("POST", "$base/api/v1/envelopes/$id/send", $key, ["confirmExclusions" => true]);Embed signing
<script src="https://your-host.example/embed.js"></script>
<div id="signing"></div>
<script>
HurrySign.embed({
url: SIGNING_URL,
container: "#signing",
height: "720px",
onEvent: function (event) { console.log(event.event, event.envelopeId); }
});
</script>
// The signing URL comes from your server:
// POST /api/v1/envelopes/:id/recipients/:recipientId/signing-url
// { "returnUrl": "https://app.example.com/claims/42", "expiresInSeconds": 900 }Verify a webhook — Node
const crypto = require("crypto");
function verifyHurrySignWebhook(secret, rawBody, header) {
const parts = Object.fromEntries(String(header).split(",").map((part) => {
const [key, ...rest] = part.split("=");
return [key, rest.join("=")];
}));
const age = Math.abs(Math.floor(Date.now() / 1000) - Number(parts.t));
if (!parts.v1 || age > 300) return false;
const expected = crypto.createHmac("sha256", secret).update(parts.t + "." + rawBody).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}Verify a webhook — PHP
<?php
function hurrysign_verify($secret, $rawBody, $header) {
$parts = [];
foreach (explode(",", $header) as $piece) {
[$k, $v] = array_pad(explode("=", $piece, 2), 2, "");
$parts[$k] = $v;
}
if (empty($parts["v1"]) || abs(time() - intval($parts["t"])) > 300) return false;
$expected = hash_hmac("sha256", $parts["t"] . "." . $rawBody, $secret);
return hash_equals($expected, $parts["v1"]);
}