Zapier records what your automations do — but those records have real limitations. Zap run history is deletable by any user, retained for a maximum of 60 days, capped at 10,000 visible runs, and controlled entirely by Zapier. There's no cryptographic proof that a run happened, that the data wasn't altered, or that the action was authorized before it executed.
For most workflows that's fine. But if you're running automations that trigger payments, approve access, move sensitive data, or act on behalf of users, a log you don't control isn't audit evidence — it's just a list of things someone wrote down.
This post shows how to attach a cryptographic signature to any Zapier action using PQ-Sign — FIPSign's post-quantum signing API. No code to deploy. One HTTP step in your existing Zap.
The problem with Zapier audit trails
When a Zap runs, Zapier records it internally. That record is useful for debugging, but it has three problems for anyone who needs real auditability: it can be deleted, it expires after 60 days, and it carries no cryptographic proof that the data is intact. It's also controlled by Zapier — it's not something you can present to an auditor, a regulator, or a counterparty as independent proof that action X occurred with data Y at time Z.
If you need that kind of verifiable record — and you probably do in any regulated or high-trust context — you need a signature.
What PQ-Sign does
PQ-Sign — part of the FIPSign suite — signs arbitrary payloads using ML-DSA (NIST FIPS 204), the post-quantum digital signature algorithm standardized by NIST in 2024. Think of it as the post-quantum successor to RSA or ECDSA: it produces a cryptographic signature that can be verified by anyone with your public key, with no shared secret, and no dependency on the service being available at verification time. When you create a project, you pick the security level — ML-DSA-44, ML-DSA-65, or ML-DSA-87 — and PQ-Sign uses that variant for every token in that project.
ML-DSA is specifically designed to remain secure against attacks from quantum computers — which is why it made it into the NIST standard. For your Zapier workflow, the practical implication is simple: the signature your Zap produces today will still be verifiable for up to a year, even as the threat landscape evolves.
Two ways to use the signature
Before going into the tutorial, it's worth being clear about what you're actually proving — because the two patterns have meaningfully different security properties.
Pattern A — Post-event record (weaker)
Trigger fires → Zap executes action → Zap calls POST /sign → stores token
The token proves that a Zap ran and that at the moment it ran, certain data had certain values. It's a tamper-evident record. What it doesn't prove is that the action was authorized before it happened — a compromised Zap could theoretically sign whatever it wanted.
Good for: audit trails, compliance records, data provenance.
Pattern B — Pre-authorization (stronger)
Zap calls POST /sign → passes token to receiving system → receiving system verifies → executes only if valid
Here the token is the authorization. The downstream system refuses to act unless it can verify a valid, unexpired signature. If the token is missing or invalid, nothing happens. This is the pattern you want for agent authorization — an AI or automation cannot execute privileged actions without a signed mandate.
Good for: agentic workflows, privileged operations, multi-party authorization.
The tutorial below implements Pattern A, which works entirely within Zapier. Pattern B requires a cooperating receiver on the other side.
Tutorial: sign every Zap action with PQ-Sign
Step 1 — Create your FIPSign account
Go to app.fipsign.dev and sign up. The free tier includes 10,000 signing operations per month, no credit card required.
Create a project and copy your API key. It will look like pqa_....
Step 2 — Build your Zap
Set up whatever trigger you want — a new row in Google Sheets, a form submission, a new email, anything. Add your existing action steps as normal.
Step 3 — Add the signing step
At the point in your Zap where you want to create the audit record, add a new action. Search for and select Webhooks by Zapier. There are two ways to configure it depending on your comfort level with JSON.
Option A — POST with Payload Type: JSON (simpler)
Best if you prefer building the request with Zapier's key/value UI rather than writing raw JSON.
Event: POST
URL: https://api.fipsign.dev/sign
Payload Type: JSON
In the Data section, add one field per row (key on the left, value on the right):
| Key | Value |
|---|---|
sub |
zapier_action_{{zap_id}} |
event |
form_submitted |
user |
{{user_email}} |
form_id |
{{form_id}} |
submitted_at |
{{submission_timestamp}} |
expiresInSeconds |
31536000 |
In the Headers section, add:
| Key | Value |
|---|---|
X-API-Key |
pqa_your_key |
With Payload Type set to JSON, Zapier sets Content-Type: application/json automatically — no need to add it manually.
Option B — Custom Request (more flexible)
Best if your payload has nested objects, or you want full control over the raw JSON. Note that Zapier describes this option as "very flexible but unforgiving" — a JSON syntax error will silently fail.
Event: Custom Request
Method: POST
URL: https://api.fipsign.dev/sign
In the Headers section:
| Key | Value |
|---|---|
X-API-Key |
pqa_your_key |
Content-Type |
application/json |
In the Data field, paste the raw JSON (Zapier will expand the {{variables}} before sending):
{
"sub": "zapier_action_{{zap_id}}",
"event": "form_submitted",
"user": "{{user_email}}",
"form_id": "{{form_id}}",
"submitted_at": "{{submission_timestamp}}",
"expiresInSeconds": 31536000
}
Replace the field names and variable references with whatever is relevant to your workflow. The sub field is the primary identifier of what's being signed — make it descriptive. The expiresInSeconds of 31,536,000 is one year, which makes sense for long-lived audit records.
Constraints to be aware of:
-
submax 128 characters - String values max 256 characters
- Max 10 custom fields per payload
-
expiresInSecondsbetween 60 and 31,536,000 (1 year maximum)
If your use case requires longer-lived credentials — device identity, licenses, long-term access grants — PQ-Sign also includes a Certificate Authority that issues post-quantum certificates (PQCert JSON or standard X.509) valid for up to 5 years, with a CA root valid for 10 years. That's a different pattern than event signing, but it's in the same API.
Step 4 — Store the token
The response from /sign will contain a token object. Add a step to store it wherever your audit records live — Google Sheets, Airtable, a database via webhook, etc.
Zapier flattens nested JSON fields from previous steps using double underscores. In the variable picker, the token fields will appear as:
-
token__payload— the signed data (base64-encoded JSON) -
token__signature— the ML-DSA signature -
token__algorithm— the ML-DSA variant your project uses (44, 65, or 87) -
token__issuedAt— Unix timestamp of when it was signed
Map each one to its own column or field in your storage step. Store all four — you need all four to verify later.
What the signing call looks like
The curl equivalent of what Zapier sends:
curl -s -X POST https://api.fipsign.dev/sign \
-H "X-API-Key: pqa_your_key" \
-H "Content-Type: application/json" \
-d '{
"sub": "zapier_action_wf_38291",
"event": "form_submitted",
"user": "alice@example.com",
"form_id": "contact_v2",
"submitted_at": "2026-08-02T14:30:00Z",
"expiresInSeconds": 31536000
}'
The response:
{
"success": true,
"token": {
"payload": "eyJzdWIiOiJ6YXBpZXJfYWN0aW9uX3dmXzM4MjkxIiwiZXZlbnQiOiJmb3JtX3N1Ym1pdHRlZCIs...",
"signature": "oi5UKsTnH3v2Pq...",
"algorithm": "ML-DSA-65",
"issuedAt": 1754143800
},
"meta": {
"algorithm": "ML-DSA-65",
"standard": "NIST FIPS 204",
"quantumResistant": true,
"expiresIn": 31536000,
"tokenCost": 1
},
"usage": {
"freeRemaining": 9999,
"packRemaining": 0,
"totalRemaining": 9999,
"month": "2026-08"
}
}
Important: This is not a JWT. The payload field is base64(JSON.stringify(your_data)), not base64url. Don't try to parse it with a JWT library — decode the base64 directly and then parse the JSON.
Verifying the token later
When you need to verify a stored token — during an audit, when a system needs to check authorization, or when something looks off — send the full token object back to /verify:
curl -s -X POST https://api.fipsign.dev/verify \
-H "X-API-Key: pqa_your_key" \
-H "Content-Type: application/json" \
-d '{
"token": {
"payload": "eyJzdWIiOiJ6YXBpZX...",
"signature": "oi5UKsTnH3v2Pq...",
"algorithm": "ML-DSA-65",
"issuedAt": 1754143800
}
}'
A valid token returns:
{
"success": true,
"valid": true,
"payload": {
"sub": "zapier_action_wf_38291",
"event": "form_submitted",
"user": "alice@example.com",
"form_id": "contact_v2",
"submitted_at": "2026-08-02T14:30:00Z",
"iat": 1754143800,
"exp": 1785679800
}
}
The verification confirms: the payload hasn't been altered, the signature is genuine, and the token hasn't expired. An invalid or tampered token returns valid: false with an error.
Pattern B — Pre-authorization: verify before you act
Pattern A stores a signed record after the action happens. Pattern B goes further: the receiving system won't execute the action at all unless it can verify a valid token first. The action is gated by the signature.
This requires something on the receiving end that can call /verify — a backend, a serverless function, or any endpoint you control. If you don't have that, Pattern A is the right choice.
The Zapier side works in two steps:
First, call /sign to sign the intent — before the action happens. Use a short expiresInSeconds (60–300 seconds) so the token can't be reused outside this specific Zap run.
Then pass the token to your backend in the next action step using Webhooks by Zapier → Custom Request. Use Custom Request here (not POST) because you need to send a nested token object — and Custom Request lets you write the full JSON structure in the Data field directly.
Event: Custom Request
Method: POST
URL: https://your-backend.com/webhook
Headers:
| Key | Value |
|---|---|
Content-Type |
application/json |
Data field — paste this JSON and use Zapier's variable picker to insert the values from the previous /sign step where indicated:
{
"action": "send_invoice",
"customer_id": "<customer_id from trigger>",
"amount": "<amount from trigger>",
"token": {
"payload": "<Step 3 › Token Payload>",
"signature": "<Step 3 › Token Signature>",
"algorithm": "<Step 3 › Token Algorithm>",
"issuedAt": "<Step 3 › Token Issued At>"
}
}
A note on how Zapier handles this: when Zapier parses the /sign response, it flattens nested fields using double underscores internally (token__payload, token__signature, etc.). In Custom Request's Data field, you select those variables from the picker and Zapier inserts their values as plain text into the raw JSON — which reconstructs the nested token object correctly on the receiving end.
The receiver side — a Node.js Express endpoint using the FIPSign SDK:
npm install fipsign-sdk
import express from 'express'
import { PQAuth } from 'fipsign-sdk'
const app = express()
const fipsign = new PQAuth({ apiKey: 'pqa_your_key' })
app.use(express.json())
app.post('/webhook', async (req, res) => {
const { action, customer_id, amount, token } = req.body
// Verify before acting — never throws, always returns { valid, payload, error }
const result = await fipsign.verify(token)
if (!result.valid) {
return res.status(401).json({ error: 'Unauthorized', reason: result.error })
}
// Token is valid — safe to act
console.log('Authorized action:', result.payload.sub, '| action:', action)
await processInvoice(customer_id, amount)
res.json({ success: true })
})
If the token is missing, expired, or tampered with, the endpoint returns 401 and nothing happens. The Zap can be configured to treat a non-2xx response as a failure and halt.
Two things worth noting about verify() from the SDK:
-
It never throws — always returns
{ valid, payload, error? }. That's why there's no try/catch in the example above. -
For higher throughput, you can initialize with
localVerify: trueand yourprojectId— verification runs entirely in memory with no API call, at the cost of not checking the revocation list. Use remote verification for sensitive operations.
// Local verification — no API call, ~1ms
const fipsign = new PQAuth({
apiKey: 'pqa_your_key',
localVerify: true,
projectId: 'proj_your_project_id', // get this from the dashboard
})
If you're using Zapier to orchestrate AI agent actions — triggering Claude, GPT, or other models to act on behalf of users — the signing pattern becomes authorization, not just logging.
The agent requests a signed token before acting. The system receiving the action verifies the token. No valid token, no action. This is the pre-authorization pattern from earlier applied to agentic workflows.
FIPSign also ships an MCP server (@fipsign/mcp) for direct integration with Claude Desktop and Claude Code, if you're building agent pipelines outside of Zapier.
Get started
Free tier at app.fipsign.dev — 10,000 signing operations per month, no credit card. Create a project, copy your API key, add one Webhooks step to your existing Zap.
Your audit trail starts with the next Zap run.
Top comments (0)