Skip to content

Webhooks

Use webhooks to get real-time updates.

ArcSite uses webhooks to notify your application when an event happens in your organization. Webhooks are particularly useful for asynchronous events such as a new drawing being created.

A webhook enables ArcSite to push real-time notifications to your app. ArcSite sends these notifications to your app as a JSON payload in an HTTP POST request. You can then use these notifications to execute actions in your backend systems.

You can create and manage your webhooks in the ArcSite web app under Admin → Developers → Webhooks. Only organization owners and admins can manage webhooks.

Webhook URLs should use https:// (plain http:// is also accepted), can be at most 255 characters, and must resolve to a public internet address: private-network, loopback and link-local addresses are rejected. You can include basic-auth credentials in the URL (https://user:pass@example.com/hook).

Each webhook payload contains the event type and the payload data.

Event payload
{
"event": "project.created",
"data": {
"id": "36029621652695040",
"name": "project 4",
"created_at": "2022-01-16T04:19:23.000000Z",
"updated_at": "2022-01-16T04:19:23.000000Z",
"archived": false,
"job_number": "144111",
"customer": {
"name": "Jack",
"phone": "1441",
"second_phone": "1122",
"email": "customer@example.com",
"second_email": null,
"address": {
"street": "street",
"city": "city",
"county": "county",
"state": "state",
"zip_code": "300433"
}
},
"work_site_address": null,
"sales_rep": {
"name": "Wang",
"email": "sales@example.com",
"phone": "122122-121"
},
"tags": ["tag1", "tag2"]
}
}
EventDescription
project.createdTriggered when a project is created.
project.updatedTriggered when a project’s own data is modified, such as renaming, descriptions, or tags.
drawing.createdTriggered when a drawing is created by uploading PDF files (on the web or with the Import PDF API) or uploading a new drawing.
drawing.updatedTriggered when a drawing is manually re-uploaded to the cloud after its initial creation.
proposal.signedTriggered when a proposal is signed in the app. (Deprecated)
proposal.sentTriggered when a proposal is sent to customer. (Deprecated)
proposal.approvedTriggered when a proposal is approved by customer. (Deprecated)
proposal.status_changedTriggered when a proposal status changes.
proposal.viewedTriggered the first time a customer opens an online proposal.
proposal.payment.receivedTriggered when a payment is successfully received for a proposal.
app_export_proposal.promptedTriggered after a proposal PDF is exported in the ArcSite App and the user answers the push prompt. Available only when your organization is connected to a supported integration; see Integrations.

ArcSite sends each event as an HTTP POST with Content-Type: application/json. A delivery succeeds when your endpoint returns a 2xx response status code within 30 seconds (the connection must be accepted within 5 seconds). Any other result, such as a non-2xx status code, a timeout or a connection error, counts as a failure, and ArcSite retries the event.

ArcSite follows up to 5 redirects (301, 302, 303, 307, 308), as long as every hop resolves to a public internet address. After a 307 or 308 it repeats the POST with the same body; after a 301, 302 or 303 it follows with a GET without a body. The response at the end of the redirects decides whether the delivery succeeded.

ArcSite makes up to 7 attempts per event: the first delivery and 6 retries, sent 15 seconds, 30 seconds, 3 minutes, 10 minutes, 20 minutes and 30 minutes after the previous failed attempt. If the last attempt fails, about 64 minutes after the first, ArcSite stops retrying the event. If your endpoint responds with 410 Gone, ArcSite stops retrying that event right away. The webhook itself stays enabled.

Every retry sends the same request body as the first attempt. The body holds the data as it was when ArcSite generated the payload, shortly after the event, so it can already include changes made after the event. The ArcSite-Signature header has a new timestamp and signature on each attempt.

We highly recommend you secure your integration by ensuring your handler verifies that all webhook request are generated by ArcSite.

The following section describes how to verify webhook signatures:

  • Retrieve your endpoint’s secret.
  • Verify the signature.

Retrieving your endpoint’s secret

Use the ArcSite Webhooks web UI to get the webhook secret. ArcSite generates a unique secret key for each webhook.

Webhooks

Sample webhook header
{
"ArcSite-Signature": "t=1716975491,v=5f43f28c3d33c34b18634399a8e3bb69dcfb9f146cea562289306d783187d0f1"
}

Verifying the signature

To verify the signature, you need to compare the signature in the header of the incoming webhook request with the signature using the secret and the request body. The ArcSite-Signature header included in each signed event contains a timestamp and signature that you must verify. The timestamp is prefixed by t=, and the signature is prefixed by a v=.

import hmac
import hashlib
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
SECRET = 'your_webhook_secret'
def verify_webhook(header, payload, secret):
if not header:
return False
# Extract timestamp and signature
elements = dict(item.split('=', 1) for item in header.split(','))
timestamp, signature = elements['t'], elements['v']
# Prepare signed payload string
signed_payload = f"{timestamp}.{payload}"
# Compute expected signature
expected_signature = hmac.new(secret.encode(), signed_payload.encode(), hashlib.sha256).hexdigest()
# Verify signature
return hmac.compare_digest(signature, expected_signature)
# Django Example
@csrf_exempt
def webhook_handler(request):
if request.method == 'POST':
header = request.headers.get('ArcSite-Signature')
payload = request.body.decode('utf-8')
if not verify_webhook(header, payload, SECRET):
return HttpResponse('Invalid signature or timestamp.', status=400)
# TODO Process the valid webhook payload here
return HttpResponse('Webhook received successfully.', status=200)
return HttpResponse('Method not allowed.', status=405)

Step 1: Extract the timestamp and signature from the header

Split the header using the , character as the separator to get a list of elements. Then split each element using the = character as the separator to get a prefix and value pair. The value for the prefix t corresponds to the timestamp and the prefix v to the signature. You can discard all other elements.

Step 2: Prepare the signed_payload string

The signed_payload string is created by concatenating:

  • The timestamp (as a string)
  • The character .
  • The actual JSON payload (that is, the request body)

Step 3: Determine the expected signature

Compute an HMAC with the SHA256 hash function. Use the webhook’s secret as the key, and use the signed_payload string as the message.

Step 4: Compare the signature

Compare the signature in the header to the expected signature.

const express = require("express");
const crypto = require("crypto");
const app = express();
const SECRET = "your_webhook_secret";
app.use(express.raw({ type: "*/*" }));
function verifyWebhook(header, payload, secret) {
// Extract timestamp and signature
const elements = header.split(",").reduce((acc, element) => {
const [key, value] = element.split("=");
acc[key] = value;
return acc;
}, {});
const signature = elements["v"];
// Prepare signed payload string
const signedPayload = `${elements["t"]}.${payload}`;
// Compute expected signature
const expectedSignature = crypto
.createHmac("sha256", secret)
.update(signedPayload)
.digest("hex");
// Verify signature
if (!signature || signature.length !== expectedSignature.length) {
return false;
}
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature),
);
}
app.post("/webhook", (req, res) => {
const header = req.headers["arcsite-signature"];
const payload = req.body.toString("utf-8");
if (!verifyWebhook(header, payload, SECRET)) {
return res.status(400).send("Invalid signature.");
}
// TODO Process the valid webhook payload here
res.status(200).send("Webhook received successfully.");
});
app.listen(3000, () => {
console.log(`Server is running on port 3000`);
});

project.created Triggered when a project is created.

ParameterTypeDescription
idStringID of the project
nameStringName of the project
created_atStringCreation time (ISO 8601, UTC, e.g. 2022-01-16T04:19:23.000000Z)
updated_atStringLast update time (ISO 8601, UTC)
archivedBooleanWhether the project is archived
customerCustomer | nullCustomer profile of the project
job_numberString | nullJob number of the project
work_site_addressAddress | nullWorksite address of the project
sales_repSalesRep | nullSales Representative of the project
tagsList[String]Tags attached to the project

project.updated Triggered when a project’s own data is modified, including:

  • Renaming the project, or updating its description / display name / client profile.
  • Adding or removing a tag on the project, or removing a tag definition that was attached to the project.

It is sent for every successful update request, even one that changes nothing. Archiving or unarchiving a project does not trigger it.

The payload schema matches project.created, with the additional source field.

ParameterTypeDescription
idStringID of the project
nameStringName of the project
created_atStringCreation time (ISO 8601, UTC, e.g. 2022-01-16T04:19:23.000000Z)
updated_atStringLast update time (ISO 8601, UTC)
archivedBooleanWhether the project is archived
customerCustomer | nullCustomer profile of the project
job_numberString | nullJob number of the project
work_site_addressAddress | nullWorksite address of the project
sales_repSalesRep | nullSales Representative of the project
tagsList[String]Tags attached to the project
sourceString | nullOrigin of the update: app for the ArcSite App, web for the ArcSite web UI, open_api for the ArcSite Open API, or null when unavailable (such as a system-initiated update)

The drawing.created webhook will be triggered when a drawing is created by uploading PDF files on the user site or with the Import PDF API, or by manually uploading some newly created drawing to the cloud from the ArcSite App.

ParameterTypeDescription
idStringID of the drawing
project_idStringProject ID of the drawing
nameStringName of the drawing
pdf_urlString | nullDownload address of PDF format of the drawing
png_urlStringDownload address of PNG format of the drawing (empty string if there is none)
dxf_urlString | nullDownload address of DXF format of the drawing
tagsList[String]Tags added to this drawing

The drawing.updated webhook is triggered whenever a drawing is manually re-uploaded to the cloud from the ArcSite App after its initial creation.

ParameterTypeDescription
idStringID of the drawing
project_idStringProject ID of the drawing
nameStringName of the drawing
pdf_urlString | nullDownload address of PDF format of the drawing
png_urlStringDownload address of PNG format of the drawing (empty string if there is none)
dxf_urlString | nullDownload address of DXF format of the drawing
tagsList[String]Tags added to this drawing

proposal.signed Triggered when a proposal is signed in the app.

ParameterTypeDescription
project_idStringProject ID of the drawing
nameStringFile name of the signed document
urlStringDownload address of the signed pdf
drawing_idStringThe associated drawing id of the signed pdf
drawing_version_idStringThe associated drawing version id of the signed pdf

proposal.sent Triggered when a proposal is sent to customer.

ParameterTypeDescription
project_idStringApproved proposal related project ID
proposal_idStringProposal ID
nameStringProposal name
customer_nameStringProposal customer name
contact_emailStringThe sales email
sales_representativeStringThe sales name
proposal_optionsList[ProposalOption]The proposal option data list
ParameterTypeDescription
idStringProposal option ID
nameStringProposal option name
drawing_idStringThe proposal option associated drawing ID
drawing_version_idStringThe proposal option associated drawing version ID
totalNumberThe total of the proposal option
pdf_urlStringDownload address of the proposal option pdf file

proposal.approved Triggered when a proposal is approved by customer.

ParameterTypeDescription
project_idStringApproved proposal related project ID
proposal_idStringProposal ID
nameStringProposal name
customer_nameStringProposal customer name
contact_emailStringThe sales email
sales_representativeStringThe sales name
customer_emailString | nullProposal customer email
customer_addressAddressCustomer address of the project
approved_optionApprovedProposalOptionApproved proposal option data
initial_proposal_idString(optional) ID of the first proposal in the change-order chain
ParameterTypeDescription
nameStringApproved proposal option name
drawing_idStringAssociated drawing ID
drawing_version_idStringAssociated drawing version ID
totalNumberTotal amount of the approved option
pdf_urlStringDownload address of the approved proposal PDF

The deprecated proposal.approved event does not include an option id in approved_option. The pdf_url expires 24 hours after ArcSite generates the payload.

proposal.status_changed Triggered when a proposal status changes.

It isn’t sent for status changes caused by payments (use proposal.payment.received), when the customer e-signs a proposal that still needs the sales representative’s signature, or when an expired proposal is reopened by setting a new expiration date.

ParameterTypeDescription
proposal_idStringProposal ID
project_idStringProposal related project ID
nameStringProposal name
statusStringProposal status. See the enum below.
sales_representativeStringThe sales name
contact_emailStringThe sales email
customer_nameStringProposal customer name
customer_emailString | nullProposal customer email
document_numberString(optional) Proposal document number
close_noteString(optional) Note explaining why proposal was closed (Only present when status is VOID or LOST)
totalNumber(optional) The total of the proposal (Only present when status is APPROVED, PARTIALLY_PAID, PAID or OVER_PAID). For a change order, this is the full revised amount; see Change Orders
pdf_urlString(optional) Download link to the proposal PDF file. Only present when status is APPROVED, PARTIALLY_PAID, PAID or OVER_PAID. Contains the signed version if the proposal has been signed. Expires 24 hours after ArcSite generates the payload
approved_optionObject(optional) Contains drawing_id (String); online approvals also include id (String) and drawing_version_id (String). Only present when status is APPROVED, PARTIALLY_PAID, PAID or OVER_PAID
initial_proposal_idString(optional) ID of the first proposal in the change-order chain. Present on every proposal in a chain, including the original proposal, where it equals proposal_id
proposal_optionsList[ProposalOption](optional) Full list of proposal options for online proposals. Included whenever proposal status changes.
template_idString | nullThe ID of the template used to create the proposal. Returns null when the proposal is signed in app.

proposal.viewed Triggered the first time a customer opens an online proposal.

Sent once per proposal, on the first open only — later opens do not re-trigger it. Use it to advance a deal stage or queue follow-up while the customer is still looking at the bid. Sales previews and sign-in-person sessions do not trigger this event, and a proposal that was signed in person does not send it later.

Example
{
"event": "proposal.viewed",
"data": {
"proposal_id": "1234567890",
"project_id": "9876543210",
"name": "Backyard Fence",
"viewed_at": "2026-07-30T10:00:00.123456Z",
"status": "PENDING",
"sales_representative": "Jane Smith",
"contact_email": "jane@example.com",
"customer_name": "John Doe",
"customer_email": "john@example.com",
"document_number": "P-0001",
"template_id": "2468013579"
}
}
ParameterTypeDescription
proposal_idStringProposal ID
project_idStringProposal related project ID
nameStringProposal name
viewed_atStringWhen the customer first opened the proposal (ISO 8601, UTC)
statusStringProposal status when the webhook payload is generated; it may have changed since the first view
sales_representativeStringThe sales name
contact_emailStringThe sales email
customer_nameStringProposal customer name
customer_emailString | nullProposal customer email
document_numberString(optional) Proposal document number
initial_proposal_idString(optional) ID of the first proposal in the change-order chain. Present on every proposal in a chain, including the original proposal, where it equals proposal_id
template_idStringThe ID of the template used to create the online proposal.

close_note, total, pdf_url and approved_option are also included under the same conditions as in proposal.status_changed.

proposal.payment.received Triggered when a payment is successfully received for a proposal.

This webhook fires for both ArcSite Payment and Mark as Paid (manual payment recording). The webhook is triggered once per payment transaction.

ArcSite Payment Example
{
"event": "proposal.payment.received",
"data": {
"proposal_id": "12345",
"paid_amount": 5000.0,
"pay_channel": "arcsite_payment",
"paid_time": "2025-11-06T10:30:00",
"drawing_id": "22444",
"payment_method": "Credit Card"
}
}
Mark as Paid Example (manual payment recording)
{
"event": "proposal.payment.received",
"data": {
"proposal_id": "12345",
"paid_amount": 2500.0,
"pay_channel": "mark_as_paid",
"paid_date": "2025-11-06",
"drawing_id": "22444",
"payment_method": "Check"
}
}
ParameterTypeDescription
proposal_idStringID of the proposal that received the payment
drawing_idStringThe approved drawing id of the proposal
paid_amountNumberAmount of this specific payment (not cumulative). Decimal with 2 decimal places
pay_channelStringPayment channel identifier: arcsite_payment (online payment) or mark_as_paid (manual recording)
paid_timeString(conditional) Timestamp (UTC) when payment was received, in ISO 8601 format without a timezone designator (e.g. 2025-11-06T10:30:00). Only present when pay_channel is arcsite_payment
paid_dateString(conditional) Date when payment was recorded. Only present when pay_channel is mark_as_paid. ISO 8601 date
payment_methodStringPayment method: Credit Card, ACH, Check, Cash, Wire or Other