Knit API Documentation
Step 4: Per-Payment Signature (CPN V2)
{{baseUrl}}/api/v1/managed-signing/requestsFor each CPN V2 payment, Circle provides a messageToBeSigned (EIP-712 typed data). You sign it using this endpoint and return the signature to Circle.
How CPN V2 Works
1. Initiate payment with Circle → Circle returns `messageToBeSigned`
2. Sign with Knit API → Knit returns `signature`
3. Submit to Circle → Circle processes the paymentHeaders
X-API-KEY: Your API key for authentication.Accept: Set toapplication/jsonto receive responses in JSON format.Content-Type: Set toapplication/json.
Request Body
{
"walletId": "<local-wallet-id>",
"network": "MATIC_MAINNET",
"type": "eip712",
"payload": {
"typedData": {
"primaryType": "PermitWitnessTransferFrom",
"domain": {
"name": "Permit2",
"chainId": 137,
"verifyingContract": "0x000000000022D473030F116dDEE9F6B43aC78BA3"
},
"types": {
"PermitWitnessTransferFrom": [
{ "name": "permitted", "type": "TokenPermissions" },
{ "name": "spender", "type": "address" },
{ "name": "nonce", "type": "uint256" },
{ "name": "deadline", "type": "uint256" },
{ "name": "witness", "type": "..." }
],
"TokenPermissions": [
{ "name": "token", "type": "address" },
{ "name": "amount", "type": "uint256" }
]
},
"message": {
"permitted": {
"token": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
"amount": "1000000"
},
"spender": "0x...",
"nonce": "...",
"deadline": "...",
"witness": "..."
}
}
}
}| Field | Type | Description |
|---|---|---|
walletId | string | Your local wallet ID |
network | string | Target network |
type | string | Must be eip712 for typed data signing |
payload.typedData | object | Circle's messageToBeSigned (EIP-712 format) |
Important
The typedData object should be exactly as provided by Circle's API. Do not modify or camelCase the keys in types or message - they must match the EIP-712 schema exactly.
Sample Request
curl --location -g '{{baseUrl}}/api/v1/managed-signing/requests' \
--header 'X-API-KEY: {{apiKey}}' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data '{
"walletId": "<local-wallet-id>",
"network": "MATIC_MAINNET",
"type": "eip712",
"payload": {
"typedData": { "...Circle messageToBeSigned..." }
}
}'Sample Response
{
"statusCode": 201,
"message": "Signing request created",
"data": {
"id": "<local-request-id>",
"businessId": "<business-id>",
"walletId": "<local-wallet-id>",
"network": "MATIC_MAINNET",
"type": "eip712",
"status": "SIGNED",
"payload": {
"typedData": { "..." }
},
"signature": "0x...",
"createdAt": "2026-01-20T18:30:40.912Z",
"updatedAt": "2026-01-20T18:30:40.912Z"
},
"success": true
}Using the Signature
Take the signature from the response and include it in Circle's payment submission step:
{
"signature": "0x..."
}Gasless Signing
Unlike the Permit2 approval step, EIP-712 signing is completely gasless. The signature is generated off-chain and no blockchain transaction is required.
Error Handling
| Error | Cause | Solution |
|---|---|---|
POLICY_DENIED | typedData not allowed by policy | Ensure typedData.allow: true in your policy |
INVALID_TYPED_DATA | Malformed EIP-712 data | Verify the typedData matches Circle's format exactly |
Integration Example
// 1. Initiate payment with Circle
const circleResponse = await circle.initiatePayment({
amount: "10.00",
currency: "USD"
});
// 2. Sign with Knit
const knitResponse = await fetch('{{baseUrl}}/api/v1/managed-signing/requests', {
method: 'POST',
headers: {
'X-API-KEY': apiKey,
'Content-Type': 'application/json'
},
body: JSON.stringify({
walletId: walletId,
network: 'MATIC_MAINNET',
type: 'eip712',
payload: {
typedData: circleResponse.messageToBeSigned
}
})
});
const { signature } = (await knitResponse.json()).data;
// 3. Submit to Circle
await circle.submitPayment({
paymentId: circleResponse.paymentId,
signature: signature
});